Skip to content

Commit ba67da5

Browse files
committed
fix(useImage): 新增useImage hooks & 新增对应文档
1 parent ec25cdd commit ba67da5

File tree

11 files changed

+371
-82
lines changed

11 files changed

+371
-82
lines changed

example/examples/src/routes/ImagePicker/index.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React from 'react';
2-
import ImagePicker, {File} from '@uiw/react-native-image-picker';
2+
import {Text} from '@uiw/react-native';
3+
import ImagePicker, {File, useImage} from '@uiw/react-native-image-picker';
34
import Layout, {Container} from '../../Layout';
45
import {ComProps} from '../../routes';
6+
import {Pressable} from 'react-native';
57

68
const {Header, Body, Card, Footer} = Layout;
79

@@ -11,6 +13,11 @@ export default function MenuDropdownView(props: ImagePickerProps) {
1113
const {route} = props || {};
1214
const description = route.params.description;
1315
const title = route.params.title;
16+
const {launchLibrary, launchCamera} = useImage({
17+
onSuccess: result => {
18+
console.log('result', result);
19+
},
20+
});
1421
return (
1522
<Container>
1623
<Layout>
@@ -67,6 +74,14 @@ export default function MenuDropdownView(props: ImagePickerProps) {
6774
}}
6875
/>
6976
</Card>
77+
<Card title="useImage">
78+
<Pressable onPress={launchLibrary}>
79+
<Text color="primary_background">打开相册</Text>
80+
</Pressable>
81+
<Pressable onPress={launchCamera}>
82+
<Text color="primary_background">打开摄像头</Text>
83+
</Pressable>
84+
</Card>
7085
</Body>
7186
<Footer />
7287
</Layout>
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
ImagePicker 图片上传
2+
---
3+
上传图片/预览图片/保存图片
4+
5+
### 使用本组件需要单独安装
6+
```bash
7+
yarn add @uiw/react-native-image-picker react-native-image-picker @react-native-camera-roll/camera-roll react-native-image-viewing
8+
```
9+
该组件依赖
10+
- `图片上传` [react-native-image-picker](https://github.com/react-native-image-picker/react-native-image-picker)
11+
- `图片预览` [react-native-image-viewing](https://github.com/jobtoday/react-native-image-viewing)
12+
- `图片保存` [@react-native-camera-roll/camera-roll](https://github.com/react-native-cameraroll/react-native-cameraroll)
13+
14+
### 权限
15+
16+
#### ios
17+
请在`ios/<应用名称>/Info.plist`中添加以下代码
18+
```xml
19+
<key>NSCameraUsageDescription</key>
20+
<string>This app requires access to the camera.</string>
21+
<key>NSPhotoLibraryAddUsageDescription</key>
22+
<string>This app requires access to the photo.</string>
23+
```
24+
25+
#### android
26+
请在`android/app/src/main/AndroidManifest.xml` `<manifest>` 标签下添加以下代码
27+
28+
##### Android 13以下
29+
在 Android 13以下,记得将`android:requestLegacyExternalStorage="true"`属性添加到`<application>`中来支持 Android 10
30+
```xml
31+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
32+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
33+
```
34+
35+
##### Android 13以上
36+
在 Android 13以上,`READ_EXTERNAL_STORAGE已`已经被`READ_MEDIA_IMAGES``READ_MEDIA_VIDEO`取代
37+
```xml
38+
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
39+
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
40+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
41+
```
42+
43+
```xml
44+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
45+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
46+
```
47+
48+
### ImagePicker 基本用法
49+
50+
```js
51+
import React from 'react';
52+
import ImagePicker, { File } from '@uiw/react-native-image-picker';
53+
import { Pressable,View } from 'react-native';
54+
const Demo = () => {
55+
return (
56+
<View>
57+
<ImagePicker
58+
upload={async(file: File[]) => {
59+
let imageList: string[] = [];
60+
await file.forEach(file => imageList.push(file.uri));
61+
return imageList;
62+
}}
63+
selectionLimit={2}
64+
/>
65+
</View>
66+
)
67+
}
68+
}
69+
```
70+
71+
### useImage 基本用法
72+
73+
```js
74+
import React from 'react';
75+
import { useImage } from '@uiw/react-native-image-picker';
76+
import { Pressable,View } from 'react-native';
77+
const Demo = () => {
78+
const { launchLibrary, launchCamera } = useImage({
79+
onSuccess: (result) => {
80+
console.log('result', result);
81+
}
82+
})
83+
return (
84+
<View>
85+
<Pressable onPress={launchLibrary}><Text color="primary_background">打开相册</Text></Pressable>
86+
<Pressable onPress={launchCamera}><Text color="primary_background">打开摄像头</Text></Pressable>
87+
</View>
88+
)
89+
}
90+
}
91+
```
92+
93+
### 参数
94+
[组件继承@react-native-camera-roll/camera-roll](https://github.com/react-native-cameraroll/react-native-cameraroll)
95+
```ts
96+
export interface File {
97+
fileName: string;
98+
fileType: string;
99+
uri: string;
100+
fileSize?: number;
101+
}
102+
export declare type ImagePickerProps = PropsWithChildren<{
103+
/** 宽度 */
104+
width?: number;
105+
/** 高度 */
106+
height?: number;
107+
/** 当前选择的图片uri */
108+
value?: string[];
109+
/** 其他图片自定义配置,详细参考react-native-image-picker的option配置 */
110+
options?: CameraOptions;
111+
/** 上传图片后是否在背景图展示,如果为 true 上传后会自动展示上传图片(此时只能上传一张) */
112+
showUploadImg?: boolean;
113+
/** 上传文件之前的钩子,参数为上传的文件,若返回 false 则停止上传,同时可以在里面执行一些上传提示操作 */
114+
beforeUpload?: (file: File[]) => boolean | ((file: File) => Promise<boolean>);
115+
/** 上传 */
116+
upload?: (file: File[]) => Promise<string[]>;
117+
/** 上传完成 */
118+
uploadFinish?: (result?: string[] | undefined) => void;
119+
/** 取消上传事件回调 */
120+
onCancel?: (response: ImagePickerResponse) => void;
121+
/** 上传失败事件回调 */
122+
onFail?: (response: ImagePickerResponse) => void;
123+
/** 授权失败的回调 */
124+
onGrantFail?: () => void;
125+
/** 预览时长按图片保存回调 */
126+
onSave?: ((image: any) => void) | undefined;
127+
/** 打开相册授权的文本 */
128+
libraryRationale?: Rationale;
129+
/** 打开摄像头授权的文本 */
130+
cameraRationale?: Rationale;
131+
/** 打开相册文本 */
132+
launchLibraryText?: string;
133+
/** 打开摄像头文本 */
134+
launchCameraText?: string;
135+
/** 从相册选择多少张图片 */
136+
selectionLimit?: number;
137+
/** 是否禁用 */
138+
disabled?: boolean;
139+
/** 是否只读 */
140+
readOnly?: boolean;
141+
/** 上传数量限制 */
142+
maxCount?: number | undefined;
143+
/** 预览图片,长按时调用的函数 */
144+
onLongPress?: (image: ImageSource) => void;
145+
/** 预览图片,更改图像索引时调用的函数 */
146+
onImageIndexChange?: (imageIndex: number) => void;
147+
/** 预览图片,Modal presentation style: default: fullScreen Android:用于overFullScreen隐藏StatusBar */
148+
presentationStyle?: ModalProps['presentationStyle'];
149+
/** 预览图片,动画模态呈现:默认fade */
150+
animationType?: ModalProps['animationType'];
151+
/** 预览图片,背景色(#000000EE) */
152+
backgroundColor?: string;
153+
/** 预览图片,向上或向下滑动关闭模式:默认true */
154+
swipeToCloseEnabled?: boolean;
155+
/** 预览图片,通过双击缩放图像:默认true */
156+
doubleTapToZoomEnabled?: boolean;
157+
/** 预览图片,在调用 onLongPress 之前以毫秒为单位的延迟:默认800 */
158+
delayLongPress?: number;
159+
/** 预览图片,标头组件,imageIndex作为道具获取当前 */
160+
HeaderComponent?: ComponentType<{
161+
imageIndex: number;
162+
}>;
163+
/** 预览图片,页脚组件,imageIndex作为道具获取当前 */
164+
FooterComponent?: ComponentType<{
165+
imageIndex: number;
166+
}>;
167+
}>;
168+
169+
170+
export type useImageProps = {
171+
/** 成功回调 */
172+
onSuccess?: (files: File[]) => void;
173+
/** 取消回调 */
174+
onCancel?: () => void;
175+
/** 错误回调 */
176+
onError?: (error: ErrorCode) => void;
177+
/** 授权失败的回调 */
178+
onGrantFail?: () => void;
179+
/** 其他图片自定义配置,详细参考react-native-image-picker的option配置 */
180+
options?: CameraOptions
181+
}
182+
```

packages/react-native-image-picker/src/ImagePicker/types.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { PropsWithChildren, ComponentType } from 'react';
22
import { Rationale, ModalProps, ImageURISource, ImageRequireSource } from 'react-native';
3-
import { CameraOptions, ImagePickerResponse } from 'react-native-image-picker';
3+
import { CameraOptions, ImagePickerResponse, ErrorCode } from 'react-native-image-picker';
44
export declare type ImageSource = ImageURISource | ImageRequireSource;
55
export interface File {
66
fileName: string;
@@ -74,3 +74,16 @@ export declare type ImagePickerProps = PropsWithChildren<{
7474
imageIndex: number;
7575
}>;
7676
}>;
77+
78+
export type useImageProps = {
79+
/** 成功回调 */
80+
onSuccess?: (files: File[]) => void;
81+
/** 取消回调 */
82+
onCancel?: () => void;
83+
/** 错误回调 */
84+
onError?: (error: ErrorCode) => void;
85+
/** 授权失败的回调 */
86+
onGrantFail?: () => void;
87+
/** 其他图片自定义配置,详细参考react-native-image-picker的option配置 */
88+
options?: CameraOptions;
89+
};
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { PermissionsAndroid, Platform } from 'react-native';
2+
import {
3+
CameraOptions,
4+
ImagePickerResponse,
5+
launchImageLibrary,
6+
launchCamera as launchRNCamera,
7+
} from 'react-native-image-picker';
8+
import { useSafeState } from 'ahooks';
9+
import type { File, useImageProps } from './types';
10+
11+
export default function useImage({ onSuccess, onCancel, onError, onGrantFail, options }: useImageProps) {
12+
/** loading */
13+
const [loading, setLoading] = useSafeState(false);
14+
15+
const libraryRationale = {
16+
title: '获取读取文件权限',
17+
message: '若不允许,您将无法访问图库',
18+
buttonPositive: '同意',
19+
buttonNegative: '取消',
20+
buttonNeutral: '下次再说',
21+
};
22+
23+
const cameraRationale = {
24+
title: '获取摄像头权限',
25+
message: '若不允许,您将无法使用摄像头功能',
26+
buttonPositive: '同意',
27+
buttonNegative: '取消',
28+
buttonNeutral: '下次再说',
29+
};
30+
31+
// 初始化图片上传配置
32+
const initialOptions: CameraOptions = {
33+
mediaType: 'photo',
34+
includeBase64: true,
35+
quality: 1,
36+
saveToPhotos: false,
37+
durationLimit: 15,
38+
videoQuality: 'high',
39+
};
40+
41+
/** 打开相册 */
42+
const launchLibrary = async () => {
43+
if (Platform.OS === 'android') {
44+
const result = await PermissionsAndroid.request(
45+
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
46+
libraryRationale,
47+
);
48+
if (result !== 'granted') {
49+
onGrantFail?.();
50+
return;
51+
}
52+
}
53+
setTimeout(() => {
54+
launchImageLibrary(
55+
{
56+
selectionLimit: 1,
57+
...initialOptions,
58+
...options,
59+
},
60+
handleCallback,
61+
);
62+
}, 200);
63+
};
64+
65+
const launchCamera = async () => {
66+
if (Platform.OS === 'android') {
67+
const result = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA, cameraRationale);
68+
if (result !== 'granted') {
69+
onGrantFail?.();
70+
return;
71+
}
72+
}
73+
setTimeout(() => {
74+
launchRNCamera(
75+
{
76+
...initialOptions,
77+
...options,
78+
},
79+
handleCallback,
80+
);
81+
}, 200);
82+
};
83+
84+
/** 打开相册或者摄像头的回调函数 */
85+
const handleCallback = async (response: ImagePickerResponse) => {
86+
try {
87+
if (response.didCancel) {
88+
// 用户取消上传 回调
89+
onCancel?.();
90+
} else if (response.errorCode) {
91+
onError?.(response.errorCode);
92+
} else {
93+
if (!response.assets || response.assets.length === 0) return;
94+
setLoading(true);
95+
let imageFiles: File[] = [];
96+
response.assets.forEach((item) => {
97+
const file: File = {
98+
fileName: item.fileName!,
99+
fileType: item.type!,
100+
uri: item.uri!,
101+
fileSize: item.fileSize!,
102+
};
103+
imageFiles.push(file);
104+
});
105+
onSuccess?.(imageFiles);
106+
setLoading(false);
107+
}
108+
} catch (error) {
109+
setLoading(false);
110+
throw new Error('图片选择器出问题了');
111+
}
112+
};
113+
return {
114+
loading,
115+
launchLibrary,
116+
launchCamera,
117+
};
118+
}

0 commit comments

Comments
 (0)