> 文档模板：v0.4.2

<p align="center">
  <h1 align="center"> <code>react-native-document-picker</code> </h1>
</p>

本项目基于 [react-native-document-picker](https://github.com/react-native-documents/document-picker) 开发。

该第三方库的仓库已迁移至 Gitcode，且支持直接从 npm 下载，新的包名为：`@react-native-ohos/react-native-document-picker` 版本所属关系如下：
| 三方库名称    | 三方库版本（npm地址）    | 发布信息     | 支持RN版本    | Autolink     | 编译API版本     | 社区基线版本    |  源码地址   |
| ------------ | ------------ | ------------------------------ | ------------- | ------------- |------------------------ | ------------- | ------------- |
| @react-native-ohos/react-native-document-picker | [~ 9.4.0](https://www.npmjs.com/package/@react-native-ohos/react-native-document-picker)     | [Gitcode Releases](https://gitcode.com/CPF-RN/rntpc_react-native-document-picker/releases) | 0.82.*/0.84.*  | 是 | API12+ | 9.3.1 | [master](https://gitcode.com/CPF-RN/rntpc_react-native-document-picker/tree/master) |
| @react-native-ohos/react-native-document-picker | [~ 9.3.2](https://www.npmjs.com/package/@react-native-ohos/react-native-document-picker)     | [Gitcode Releases](https://gitcode.com/CPF-RN/rntpc_react-native-document-picker/releases) | 0.77.* | 否 | API12+ | 9.3.1 | [br_rnoh0.77](https://gitcode.com/CPF-RN/rntpc_react-native-document-picker/tree/br_rnoh0.77) |
| @react-native-ohos/react-native-document-picker | [~ 9.2.2](https://www.npmjs.com/package/@react-native-ohos/react-native-document-picker)     | [Gitcode Releases](https://gitcode.com/CPF-RN/rntpc_react-native-document-picker/releases) | 0.72.* | 是 | API12+ | 9.2.0 | [br_rnoh0.72](https://gitcode.com/CPF-RN/rntpc_react-native-document-picker/tree/br_rnoh0.72) |
| @react-native-oh-tpl/react-native-document-picker | [<= 9.2.0-0.0.2@deprecated](https://www.npmjs.com/package/@react-native-oh-tpl/react-native-document-picker)      | [Github Releases(deprecated)](https://github.com/react-native-oh-library/document-picker/releases) | 0.72.* | 否 | API12+ | 9.2.0 | [sig](https://github.com/react-native-oh-library/document-picker) |


## 简介

Document Picker 组件用于 React Native。<br/>
提供文件选择器功能，支持选择单个或多个文件、选择目录，并允许通过文件类型进行筛选。

## 下载安装

进入到工程目录并输入以下命令：

<!-- tabs:start -->

**npm**

```bash
npm install @react-native-ohos/react-native-document-picker
```

**yarn**

```bash
yarn add @react-native-ohos/react-native-document-picker
```

<!-- tabs:end -->

下面的代码展示了这个库的基本使用场景：

> [!WARNING] 使用时 import 的库名不变。

```javascript
import React, { useState } from "react";
import { Text, TouchableOpacity, View, StyleSheet, Switch, ScrollView } from 'react-native';
import { pick, types, pickDirectory, pickSingle, DocumentPickerOptions } from 'react-native-document-picker';

const typeList = Object.keys(types);

interface MultiSelectProps {
  onSelectValue?: (val: string[]) => void
}

interface UiSelItem {
  label: keyof typeof types,
  selected: boolean,
  index: number
}

type DirType = 'documentDirectory' | 'cachesDirectory'

interface DirOpt {
  label: DirType,
  selected: boolean
}

const MultiSelect: React.FC<MultiSelectProps> = ({ onSelectValue }) => {

  const [typeUi, setTypeUi] = useState<UiSelItem[]>(typeList.map((val, index) => ({
    label: val as keyof typeof types,
    selected: false,
    index
  })))

  const onClickSelLabel = (val: typeof typeUi[0]) => {
    // 选择allfile不行选择其它的
    if (typeUi.find(t => t.label === 'allFiles')?.selected && val.label !== 'allFiles') {
      return;
    }
    let newList = [];
    if (val.label === 'allFiles') {
      newList = typeUi.map(s => {
        return s.label === 'allFiles' ? { ...s, selected: !s.selected } : { ...s, selected: false }
      })
    } else {
      newList = typeUi.map(s => {
        return val.index === s.index ? { ...s, selected: !s.selected } : { ...s }
      })
    }
    const extList = newList.filter(t => t.selected).map(t => types[t.label]).reduce((res, typeStr) => {
      res.push(...typeStr.split(' '))
      return res;
    }, [] as string[]);
    if (onSelectValue) {
      onSelectValue(extList)
    }
    setTypeUi(newList);
  }

  return <>
    <View style={{ display: 'flex', flexWrap: 'wrap', flexDirection: 'row', rowGap: 14 }}>
      <View style={{ width: '100%' }}>
        <Text style={{ fontSize: 20, fontWeight: '600', margin: 6 }}>picker 的文件类型</Text>
      </View>
      {
        typeUi.map((s:any) =>
          <TouchableOpacity key={s.label} onPress={() => {
            onClickSelLabel(s);
          }} >
            <View style={s.selected ? styles.selectBtnActive : styles.selectBtn} >
              <Text>
                {s.label}
              </Text>
            </View>
          </TouchableOpacity>
        )
      }
    </View>
  </>
}

export default function DocumentPickerDemo(): JSX.Element {

  const [pickResult, setPickResult] = useState('');
  // 是否允许多选
  const [allowMultiSelection, setAllowMultiSelection] = useState(true);
  // 选择文件类型
  const [fileTypes, setFileTypes] = useState<string[]>([]);
  // copyTo 文件夹
  const [dirUi, setDirui] = useState<Array<DirOpt>>([
    { label: 'documentDirectory', selected: false },
    { label: 'cachesDirectory', selected: false },
  ]);

  const copyTo = dirUi.find(d => d.selected)?.label;

  const pickOpt: DocumentPickerOptions<'harmony'> = {
    allowMultiSelection,
  };
  if (copyTo) {
    pickOpt.copyTo = copyTo;
  }
  if (fileTypes.length) {
    pickOpt.type = fileTypes;
  }

  const onDirSelect = (val: DirOpt) => {
    const newUiList = dirUi.map(d => {
      if (val.label === d.label) {
        return { ...d, selected: !d.selected }
      } else {
        return { ...d, selected: false }
      }
    });
    setDirui(newUiList);
  }

  const pickFile = async () => {
    try {
      const res = await pick(pickOpt);
      setPickResult(JSON.stringify(res));
    } catch (err) {
      console.log(err);
    }
  }

  const pickS = async () => {
    try {
      const res = await pickSingle(pickOpt);
      setPickResult(JSON.stringify(res));
    } catch (err) {
      console.log(err);
    }
  }

  const pickDir = async () => {
    const res = await pickDirectory();
    console.log(res);
  }

  return <ScrollView>
    <Text>
      {JSON.stringify(pickOpt)}
    </Text>

    <MultiSelect onSelectValue={setFileTypes}></MultiSelect>

    <View style={{ width: '100%' }}>
      <Text style={{ fontSize: 20, fontWeight: '600', margin: 6 }}>是否多选</Text>
    </View>

    <Switch value={allowMultiSelection} onValueChange={setAllowMultiSelection}></Switch>

    <View style={{ width: '100%' }}>
      <Text style={{ fontSize: 20, fontWeight: '600', margin: 6 }}>copyTo文件夹</Text>
    </View>

    <View style={{ display: 'flex', flexWrap: 'wrap', flexDirection: 'row', rowGap: 14 }}>
      {
        dirUi.map(s =>
          <TouchableOpacity key={s.label} onPress={() => {
            onDirSelect(s);
          }} >
            <View style={s.selected ? styles.selectBtnActive : styles.selectBtn} >
              <Text>
                {s.label}
              </Text>
            </View>
          </TouchableOpacity>
        )
      }
    </View>

    <TouchableOpacity onPress={pickFile} style={styles.btn}>
      <Text
        style={styles.btnText}>
        pick file
        </Text>
    </TouchableOpacity>
    <TouchableOpacity onPress={pickS} style={styles.btn}>
      <Text
        style={styles.btnText}>
        pick file single
        </Text>
    </TouchableOpacity>
    <TouchableOpacity onPress={pickDir} style={styles.btn}>
      <Text
        style={styles.btnText}>
        pick Dir
        </Text>
    </TouchableOpacity>
    <View style={{ width: '100%' }}>
      <Text style={{ fontSize: 20, fontWeight: '600', margin: 6 }}>选择结果</Text>
    </View>
    <Text>
      {pickResult}
    </Text>
  </ScrollView>
}

const styles = StyleSheet.create({
  TextInput: { height: 40, borderColor: '#ccc', borderWidth: 1, borderRadius: 4, width: '90%' },
  btn: { borderRadius: 10, display: 'flex', justifyContent: 'center', alignItems: 'center', padding: 10, margin: 10, backgroundColor: 'blue' },
  btnText: { fontWeight: 'bold', color: '#fff', fontSize: 20 },
  selectBtn: { padding: 8, margin: 3, fontSize: 18, borderWidth: 1, borderRadius: 8, borderColor: '#753c13' },
  selectBtnActive: { padding: 8, margin: 3, backgroundColor: '#e2803b', fontSize: 18, borderRadius: 8, borderWidth: 1 }
});


```

## Link

|                           | 是否支持autolink | RN框架版本 |
|---------------------------|------------------|--------|
| ~9.4.0                    |  Yes             | 0.82/0.84   |

使用AutoLink的工程需要根据该文档配置，Autolink框架指导文档：https://gitcode.com/CPF-RN/ohos_react_native/blob/master/docs/zh-cn/Autolinking.md

如您使用的版本支持 Autolink，并且工程已接入 Autolink，可跳过ManualLink配置。
<details>
  <summary>ManualLink: 此步骤为手动配置原生依赖项的指导</summary>

首先需要使用 DevEco Studio 打开项目里的 HarmonyOS 工程 `harmony`。

### Overrides RN SDK

为了让工程依赖同一个版本的 RN SDK，需要在工程根目录的 `harmony/oh-package.json5` 添加 overrides 字段，指向工程需要使用的 RN SDK 版本。替换的版本既可以是一个具体的版本号，也可以是一个模糊版本，还可以是本地存在的 HAR 包或源码目录。

关于该字段的作用请阅读[官方说明](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-oh-package-json5-V5#zh-cn_topic_0000001792256137_overrides)


```json
{
  "overrides": {
    "@rnoh/react-native-openharmony": "^0.82.18" // ohpm 在线版本
    // "@rnoh/react-native-openharmony" : "./react_native_openharmony.har" // 指向本地 har 包的路径
    // "@rnoh/react-native-openharmony" : "./react_native_openharmony" // 指向源码路径
  }
}
```

### 引入原生端代码

目前有两种方法：

1. 通过 har 包引入；
2. 直接链接源码。

方法一：通过 har 包引入

> [!TIP] har 包位于三方库安装路径的 `harmony` 文件夹下。

打开 `entry/oh-package.json5`，添加以下依赖

```json
"dependencies": {
    "@rnoh/react-native-openharmony": "file:../react_native_openharmony",
    "@react-native-ohos/react-native-document-picker": "file:../../node_modules/@react-native-ohos/react-native-document-picker/harmony/document_picker.har"
  }
```

点击右上角的 `sync` 按钮

或者在终端执行：

```bash
cd entry
ohpm install
```

方法二：直接链接源码

> [!TIP] 如需使用直接链接源码，请参考[直接链接源码说明](https://gitcode.com/CPF-RN/usage-docs/blob/master/zh-cn/link-source-code.md)

### 配置 CMakeLists 和引入 DocumentPickerPackage

打开 `entry/src/main/cpp/CMakeLists.txt`，添加：

```diff
+ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")

# RNOH_BEGIN: manual_package_linking_1
+ add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-document-picker/src/main/cpp" ./document_picker)
# RNOH_END: manual_package_linking_1

# RNOH_BEGIN: manual_package_linking_2
+ target_link_libraries(rnoh_app PUBLIC rnoh_document_picker)
# RNOH_END: manual_package_linking_2
```

打开 `entry/src/main/cpp/PackageProvider.cpp`，添加：

```diff
#include "RNOH/PackageProvider.h"
#include "generated/RNOHGeneratedPackage.h"
+ #include "DocumentPickerPackage.h"

using namespace rnoh;

std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
    return {
      std::make_shared<RNOHGeneratedPackage>(ctx),
+     std::make_shared<DocumentPickerPackage>(ctx)
    };
}
```

打开 `entry/src/main/ets/RNPackagesFactory.ts`，添加：

```diff
  ...
+ import { DocumentPickerPackage } from '@react-native-ohos/react-native-document-picker/ts';

export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
  return [
    new SamplePackage(ctx),
+   new DocumentPickerPackage(ctx)
  ];
}
```
</details>

### 运行

点击右上角的 `sync` 按钮

或者在终端执行：

```bash
cd entry
ohpm install
```

然后编译、运行即可。

## 约束与限制
### 兼容性

要使用此库，需要使用正确的 React-Native 和 RNOH 版本。另外，还需要使用配套的 DevEco Studio 和 手机 ROM。

在以下版本验证通过：

1. RNOH: 0.82.1; SDK: HarmonyOS 6.0.1 Release SDK; IDE: DevEco Studio 6.0.1 Release; ROM:6.0.0.120;
2. RNOH: 0.84.1; SDK: HarmonyOS 6.0.1 Release SDK; IDE: DevEco Studio 6.0.1 Release; ROM:6.0.0.120;

### 编译运行API要求
> [!TIP] 当前三方库所有版本均已实现版本隔离，支持在 `API12+` 工程编译，及 `API12+` ROM运行。
> 
> [!TIP] 以下功能依赖特定版本的API，使用 `低于API20版本的工程编译` 或 `低于API20版本的ROM运行` 均可能导致部分功能受限。
1. 本库引入[createStreamSync](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fscreatestreamsync)，实现以同步方法基于文件路径创建文件流，此API需要在支持`API20+`的工程编译，并在支持`API20+`的ROM上运行，方可生效。

## 接口说明
### 属性

> [!TIP] "Platform"列表示该属性在原三方库上支持的平台。

> [!TIP] "HarmonyOS Support"列为 yes 表示 HarmonyOS 平台支持该属性；no 则表示不支持；partially 表示部分支持。使用方法跨平台一致，效果对标 iOS 或 Android 的效果。

 option pick方法的传参选项

| Name | Description | Type | Required | Platform | HarmonyOS Support  |
| ---- | ----------- | ---- | -------- | -------- | ------------------ |
| allowMultiSelection  | 是否允许选择多个文件。对于 pick 方法，默认为 false。对于 pickSingle 方法，allowMultiSelection 为 false 且不可覆盖。        | boolean  | no | IOS/Android      | yes |
| type  | 允许选择的文件类型。可以是字符串数组或单个字符串。   | string or Array<string>  | no | IOS/Android      | yes |
| copyTo  | 将选中的文件复制到指定文件夹。   | "cachesDirectory" \| "documentDirectory"  | no | IOS/Android  | yes |
| presentationStyle  | 控制选择器的展示方式，例如在 iPad 上可能需要全屏展示。默认为 pageSheet。   | 'fullScreen' \| 'pageSheet' \| 'formSheet' \| 'overFullScreen'  | no | IOS  | no |
| transitionStyle  | 配置选择器的过渡动画样式。默认为 coverVertical。   | 'coverVertical' \| 'flipHorizontal' \| 'crossDissolve' \| 'partialCurl'  | no | IOS  | no |
| mode  | 默认为 import。如果设置为 import，则文档选择器会将文件从外部导入到沙盒内；如果设置为 open，则文档选择器会原地打开文件。   | "import" \| "open"  | no | IOS  | no |

### API

> [!TIP] "Platform"列表示该属性在原三方库上支持的平台。

> [!TIP] "HarmonyOS Support"列为 yes 表示 HarmonyOS 平台支持该属性；no 则表示不支持；partially 表示部分支持。使用方法跨平台一致，效果对标 iOS 或 Android 的效果。


| Name           | Description                   | Type | Required | Platform    | HarmonyOS Support |
|----------------|-------------------------------| -- | -------- | ----------- | ----------------- |
| pick    | 选择文件。 | function | No       | IOS/Android | yes               |
| pickSingle       | 选择单个文件。       | function | No       | IOS/Android | yes  |
| pickDirectory       | 打开目录选择器。       | function | No       | IOS/Android | no  |
| isCancel       | 如果用户取消了文档选择器而未选择文件（在 Android 上按系统返回键，或在 iOS 上点击取消按钮），Promise 将被拒绝并返回一个取消错误。你可以使用 DocumentPicker.isCancel(err) 来检查此错误。       | function | No       | IOS/Android | yes  |
| isInProgress       | 如果用户以某种方式打开了多个文件选择器（例如应用无响应时），则只有最后一个打开的选择器的结果会被采纳，之前打开的选择器的 Promise 将被拒绝并返回一个错误，你可以使用 DocumentPicker.isInProgress() 来检查此错误。   | function | No       | IOS/Android | yes  |
| releaseSecureAccess  | 如果 mode 设置为 open，iOS 会授予你访问沙盒外部文件的安全访问权限。在这种情况下，Apple 要求你在使用完资源后尽快释放该访问权限。   | function | No       | IOS | no  |
| types       | 文件类型对象，例如 type.images、types.pdf。   | function | No       | IOS/Android | yes  |
| perPlatformTypes       | 不同平台的文件类型对象映射。   | function | No       | IOS/Android | yes  |


## 遗留问题

- [ ]  HarmonyOS 端file picker selectMode设置选文件夹无效: [issue#1](https://github.com/react-native-oh-library/document-picker/issues/1) 
- [ ] releaseSecureAccess选择沙箱路径外文件无法实现， HarmonyOS 暂无此能力接口: [issue#2](https://github.com/react-native-oh-library/document-picker/issues/2)

## 其他
- 因权限问题无法读写图库资源，文件管理中从图库选择文件暂不支持。

## 目录结构
````
/rntpc_react-native-document-picker  # 项目根目录
├── harmony              # 鸿蒙适配代码
│    └─ document_picker.har          # har包
│    └─ document_picker                  # 鸿蒙适配核心代码
│          └─ Index.ets    # 鸿蒙适配代码入口    
│          └─ src/main/ets  
│              └─ DocumentPicker  # DocumentPicker组件   
├── src                  # RN代码
│    └─ index.tsx  # 入口文件 
│    └─ index.harmony.ts  # 鸿蒙入口文件
│    └─ NativeDocumentPicker.ts # 类型文件          
│    └─ fileTypes.ts # 文件类型          
├── README.md           # 中文安装使用方法    
├── README_en.md   # 英文安装使用方法                    
````
  
## 贡献代码

使用过程中发现任何问题都可以提交 [Issue](https://gitcode.com/CPF-RN/rntpc_react-native-document-picker/issues)，当然，也非常欢迎提交 [PR](https://gitcode.com/CPF-RN/rntpc_react-native-document-picker/pulls) 。
  
## 开源协议

本项目基于 [The MIT License (MIT)](https://github.com/react-native-documents/document-picker/blob/v9.2.0/LICENSE.md) ，请自由地享受和参与开源。
