FilePicker适配FAF新框架

Signed-off-by: wonder <769425456@qq.com>
This commit is contained in:
w00356339
2023-08-07 18:25:18 +08:00
parent 4fa958fcd6
commit aa897df776
45 changed files with 1965 additions and 1702 deletions
+8 -6
View File
@@ -1,6 +1,8 @@
/.hvigor
/node_modules
/local.properties
/.idea
**/build
/tsconfig.json
/.hvigor
/node_modules
/local.properties
/.idea
**/build
/tsconfig.json
/oh_modules/
oh-package-lock.json5
+1 -2
View File
@@ -5,7 +5,6 @@
"versionCode": 10100300,
"versionName": "1.1.0.300",
"icon": "$media:app_icon",
"label": "$string:app_name",
"distributedNotificationEnabled": true
"label": "$string:app_name"
}
}
+2 -2
View File
@@ -38,10 +38,10 @@
<filefilter name="copyrightPolicyFilter" desc="Filters for copyright header policies">
<filteritem type="filename" name="README|README_zh|.*.log|.*.json5|.*.json" desc=""/>
<filteritem type="filename" name="README|README_zh|hvigorw|.*.bat|.*.log|.*.json5|.*.json" desc=""/>
</filefilter>
<filefilter name="defaultPolicyFilter" desc="Filters for LICENSE file policies">
<filteritem type="filename" name="README|README_zh|*.log|*.json5|*.json" desc="json file"/>
<filteritem type="filename" name="README|README_zh|hvigorw|*.bat|*.log|*.json5|*.json" desc="json file"/>
</filefilter>
</filefilterlist>
</oatconfig>
+26 -25
View File
@@ -1,26 +1,27 @@
{
"app": {
"compileSdkVersion": 10,
"compatibleSdkVersion": 9,
"products": [
{
"name": "default",
"signingConfig": "default",
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
}
]
{
"app": {
"products": [
{
"name": "default",
"signingConfig": "default",
"compileSdkVersion": 10,
"compatibleSdkVersion": 10,
"runtimeOS": "OpenHarmony"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
}
]
}
+2 -1
View File
@@ -1,4 +1,5 @@
/node_modules
/.preview
/build
/.cxx
/.cxx
/oh_modules
@@ -2,13 +2,8 @@
"license": "ISC",
"devDependencies": {},
"name": "entry",
"ohos": {
"org": "huawei",
"directoryLevel": "module",
"buildTool": "hvigor"
},
"description": "example description",
"repository": {},
"version": "1.0.0",
"dependencies": {}
}
}
-5
View File
@@ -1,5 +0,0 @@
{
"name": "entry",
"version": "1.0.0",
"lockfileVersion": 1
}
@@ -0,0 +1,90 @@
/*
* Copyright (c) 2021-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const MILLISECOND = {
ONE_MILLISECOND: 1,
ONE_SECOND: 1000,
ONE_MINUTE: 60 * 1000,
ONE_HOUR: 60 * 60 * 1000,
ONE_DAY: 24 * 60 * 60 * 1000,
ONE_MONTH: 30 * 24 * 60 * 60 * 1000
}
export const BYTE = {
ONE_KB: 1024,
ONE_MB: 1024 * 1024,
ONE_GB: 1024 * 1024 * 1024,
ONE_TB: 1024 * 1024 * 1024 * 1024
}
export const FILENAME_REGEXP = /^[^\\/:*?<>\"|]+$/
export const FILENAME_MAX_LENGTH = 225
/**
* 重命名连接符
*/
export const RENAME_CONNECT_CHARACTER = ' ';
export const DOCS_FOLDER = 'Docs'
export const DESKTOP_FOLDER = 'Desktop'
export const DOCUMENTS_FOLDER = 'Documents'
/**
* 内部存储根目录Uri
*/
export const INTERNAL_STORAGE_ROOT_URI: string = 'file://media/root';
/**
* picker支持的文件选择模式
*/
export const SELECT_MODE = {
FILE: 0,
FOLDER: 1,
MIX: 2
}
/**
* 文件后缀相关常量定义
*/
export const FILE_SUFFIX = {
SUFFIX_SPLIT: ',',
SUFFIX_START: '.'
}
/**
* 目录层级定义
*/
export const FOLDER_LEVEL = {
MIN_LEVEL: 1,
MAX_LEVEL: 21
}
/**
* 页面类型
*/
export const PAGE_TYPE = {
MY_PHONE : 'myPhone'
}
export const FILE_MANAGER_PREFERENCES = {
name: 'FileManagerPreferences',
lastSelectPath: {
key: 'lastSelectPath',
defaultValue: ''
}
}
@@ -63,7 +63,11 @@ namespace ErrorCodeConst {
/**
* 其他未知错误
*/
OTHER_ERROR = 9001
OTHER_ERROR = 9001,
/**
* 创建正常
*/
NORMAL = 1000
}
}
@@ -17,11 +17,14 @@ import fileAccess from '@ohos.file.fileAccess'
import Logger from '../log/Logger'
import abilityAccessCtrl from '@ohos.abilityAccessCtrl'
import { Permissions } from '@ohos.abilityAccessCtrl'
import BundleManager from '@ohos.bundle.bundleManager'
import FileShare from '@ohos.fileshare'
import wantConstant from '@ohos.app.ability.wantConstant'
import ErrorCodeConst from '../constants/ErrorCodeConst'
import MediaLibrary from '@ohos.multimedia.mediaLibrary'
import { FILE_MANAGER_PREFERENCES, FILE_SUFFIX, SELECT_MODE } from '../constants/Constant'
import StringUtil from './StringUtil'
import { ArrayUtil } from './ArrayUtil'
import { getPreferences } from './PreferencesUtil'
const TAG = 'AbilityCommonUtil'
@@ -46,11 +49,23 @@ namespace AbilityCommonUtil {
*/
export const CALLER_UID = 'ohos.aafwk.param.callerUid'
export const CALLER_BUNDLE_NAME = 'ohos.aafwk.param.callerBundleName'
/**
* 最大选择文件的个数
*/
export const MAX_FILE_PICK_NUM = 500
/**
* 后缀最大长度,包括'.'
*/
export const SUFFIX_MAX_LENGTH: number = 255;
/**
* 三方传入的后缀数组长度最大100
*/
export const SUFFIX_LIST_MAX_LENGTH: number = 100;
/**
* picker对外返回的响应码
*/
@@ -68,10 +83,11 @@ namespace AbilityCommonUtil {
* 拉起Ability时必要的初始化操作
*/
export function init(): Promise<void[]> {
const fileAccessHelperPromise = createFileAccessHelper()
getMediaLibrary()
const getRequestPermission = requestPermission()
return Promise.all([fileAccessHelperPromise, getRequestPermission])
const fileAccessHelperPromise = createFileAccessHelper();
getMediaLibrary();
const getRequestPermission = requestPermission();
const initData = initLastSelectPath();
return Promise.all([fileAccessHelperPromise, getRequestPermission, initData]);
}
/**
@@ -92,7 +108,7 @@ namespace AbilityCommonUtil {
let isDone = result.done
while (!isDone) {
const rootInfo: fileAccess.RootInfo = result.value
Logger.i(TAG, 'RootInfo: ' + rootInfo.uri + ', ' + rootInfo.deviceType + ', ' + rootInfo.deviceFlags + ', ' + rootInfo.displayName)
Logger.i(TAG, 'RootInfo: ' + rootInfo.uri + ', ' + rootInfo.deviceType + ', ' + rootInfo.deviceFlags + ', ' + rootInfo.displayName+','+rootInfo.relativePath)
rootInfoArr.push(rootInfo)
result = rootIterator.next()
isDone = result.done
@@ -106,6 +122,31 @@ namespace AbilityCommonUtil {
})
}
/**
* Ability初始化时,加载最新保存的路径Uri
*/
export function initLastSelectPath(): Promise<void> {
return new Promise((resolve, reject) => {
const defaultValue = FILE_MANAGER_PREFERENCES.lastSelectPath.defaultValue;
const lastSelectPathKey = FILE_MANAGER_PREFERENCES.lastSelectPath.key;
getPreferences(FILE_MANAGER_PREFERENCES.name).then(preferences => {
preferences.get(lastSelectPathKey, defaultValue).then((result: string) => {
AppStorage.SetOrCreate<string>(lastSelectPathKey, result);
resolve();
Logger.i(TAG, 'initLastSelectPath result: ' + result);
}).catch((error) => {
AppStorage.SetOrCreate<string>(lastSelectPathKey, defaultValue);
Logger.e(TAG, 'initLastSelectPath preferences.get fail, error:' + JSON.stringify(error));
resolve();
})
}).catch(err => {
AppStorage.SetOrCreate<string>(lastSelectPathKey, defaultValue);
Logger.e(TAG, 'initLastSelectPath getPreferences fail, error: ' + JSON.stringify(err));
resolve();
})
})
}
/**
* 申请文件管理器需用户授权的权限
*/
@@ -126,27 +167,6 @@ namespace AbilityCommonUtil {
}
}
/**
* 根据给定的uid获取对应的bundleName
*/
export function getBundleNameByUid(uid: number): Promise<string> {
if (!uid && uid !== 0) {
Logger.e(TAG, `getBundleNameByUid fail, uid is null`)
return Promise.resolve('')
}
try {
return BundleManager.getBundleNameByUid(uid).then((bundleName) => {
Logger.i(TAG, `getBundleNameByUid success, uid: ${uid}, bundleName: ${bundleName}`)
return bundleName
}).catch(error => {
Logger.e(TAG, `getBundleNameByUid fail, uid: ${uid}, error: ${JSON.stringify(error)}`)
return Promise.reject(error)
})
} catch (error) {
Logger.e(TAG, `getBundleNameByUid error, uid: ${uid}, error: ${JSON.stringify(error)}`)
return Promise.resolve('')
}
}
/**
* uri授权
* @param uriList 待授权的uri列表
@@ -155,19 +175,23 @@ namespace AbilityCommonUtil {
*/
export function grantUriPermission(uriList: Array<string>, bundleName: string, flag: wantConstant.Flags): Promise<boolean> {
return new Promise(async (resolve, reject) => {
Logger.i(TAG, "grantUriPermission start,grantSize = " + uriList?.length);
let grantSuccessCount: number = 0;
for (let uri of uriList) {
try {
await FileShare.grantUriPermission(uri, bundleName, flag)
if (flag === wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION) {
await FileShare.grantUriPermission(uri, bundleName, wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION)
await FileShare.grantUriPermission(uri, bundleName, flag | wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION)
} else {
await FileShare.grantUriPermission(uri, bundleName, flag)
}
Logger.d(TAG, `grantUriPermission success, uri: ${uri}`)
grantSuccessCount++;
} catch (error) {
resolve(false)
Logger.e(TAG, `grantUriPermission fail, uri: ${uri}, error: ${JSON.stringify(error)}`)
Logger.e(TAG, `grantUriPermission fail,grantSuccessCount:${grantSuccessCount}}, uri: ${uri}, error: ${JSON.stringify(error)}`)
return
}
}
}
Logger.i(TAG, "grantUriPermission end,grantSuccessCount = " + grantSuccessCount);
resolve(true)
})
@@ -180,7 +204,7 @@ namespace AbilityCommonUtil {
* @param message
*/
export async function terminateFilePicker(result: Array<string> = [], displayNames: Array<string> = [], resultCode: number = RESULT_CODE.SUCCESS, message: string = ''): Promise<void> {
const bundleName = await AbilityCommonUtil.getBundleNameByUid(globalThis.pickerCallerUid)
const bundleName = globalThis.pickerCallerBundleName
if (result.length && bundleName) {
// uri授权
const isSuccess = await grantUriPermission(result, bundleName, wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION)
@@ -201,16 +225,16 @@ namespace AbilityCommonUtil {
'select_item_list': result,
'file_name_list': displayNames,
message: message,
'result': result[0],
'result': result[0],
}
}
}
globalThis.abilityContext.terminateSelfWithResult(abilityResult, (error) => {
if (error.code) {
Logger.e(TAG, 'Operation failed. Cause: ' + JSON.stringify(error))
Logger.e(TAG, 'terminateFilePicker failed. Cause: ' + JSON.stringify(error))
return
}
Logger.d(TAG, 'Operation failed. Cause: ' + JSON.stringify(abilityResult))
Logger.d(TAG, 'terminateFilePicker success. result: ' + JSON.stringify(abilityResult))
})
}
/**
@@ -220,10 +244,11 @@ namespace AbilityCommonUtil {
* @param message
*/
export async function terminatePathPicker(result: Array<string>, resultCode: number = RESULT_CODE.SUCCESS, message: string = ''): Promise<void> {
const bundleName = await AbilityCommonUtil.getBundleNameByUid(globalThis.pathCallerUid)
const bundleName = globalThis.pathCallerBundleName
if (result.length && bundleName) {
// uri授权
const isSuccess = await grantUriPermission(result, bundleName, wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION)
const flag = wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION | wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION
const isSuccess = await grantUriPermission(result, bundleName, flag)
if (!isSuccess) {
resultCode = ErrorCodeConst.PICKER.GRANT_URI_PERMISSION_FAIL,
result = []
@@ -246,10 +271,10 @@ namespace AbilityCommonUtil {
}
globalThis.pathAbilityContext.terminateSelfWithResult(abilityResult, (error) => {
if (error.code) {
Logger.e(TAG, 'Operation failed. Cause: ' + JSON.stringify(error))
Logger.e(TAG, 'terminatePathPicker failed. Cause: ' + JSON.stringify(error))
return
}
Logger.d(TAG, 'Operation failed. Cause: ' + JSON.stringify(abilityResult))
Logger.d(TAG, 'terminatePathPicker success. result: ' + JSON.stringify(abilityResult))
})
}
@@ -257,11 +282,13 @@ namespace AbilityCommonUtil {
* 获取选择文件的最大个数
* @param num 调用方传入的个数
*/
export function getPickFileNum(num: number): number {
if(!num || num > MAX_FILE_PICK_NUM){
return MAX_FILE_PICK_NUM
export function getPickFileNum(num: any): number {
if (typeof num === 'number') {
if (num > 0 && num <= MAX_FILE_PICK_NUM) {
return num;
}
}
return num
return MAX_FILE_PICK_NUM;
}
/**
@@ -280,6 +307,76 @@ namespace AbilityCommonUtil {
return typeList.filter(item => item)
}
/**
* 获取选择文件Mode,默认选择文件
* @param keySelectMode 调用方传入文件mode
*/
export function getKeySelectMode(keySelectMode: any): number {
if (typeof keySelectMode === 'number') {
if (keySelectMode === SELECT_MODE.FILE
|| keySelectMode === SELECT_MODE.FOLDER
|| keySelectMode === SELECT_MODE.MIX) {
return keySelectMode;
}
}
return SELECT_MODE.FILE;
}
/**
* 获取支持的文件后缀列表
* @param keyFileSuffixFilter 调用方传入文件后缀列表
*/
export function getKeyFileSuffixFilter(keyFileSuffixFilter: string[]): Array<string> {
let suffixList = [];
if (!ArrayUtil.isEmpty(keyFileSuffixFilter)) {
let len = keyFileSuffixFilter.length;
let size = len > SUFFIX_LIST_MAX_LENGTH ? SUFFIX_LIST_MAX_LENGTH : len;
for (let index = 0; index < size; index++) {
const suffixStr = keyFileSuffixFilter[index];
if (typeof suffixStr === 'string') {
const suffixArray = suffixStr.split(FILE_SUFFIX.SUFFIX_SPLIT);
for (let index = 0; index < suffixArray.length; index++) {
const suffix = suffixArray[index];
if (checkFileSuffix(suffix)) {
suffixList.push(suffix.toUpperCase())
}
}
}
}
}
return suffixList.filter((item, index, array) => {
return array.indexOf(item) === index;
});
}
export function checkFileSuffix(fileSuffix: String): boolean {
return fileSuffix && fileSuffix.length <= SUFFIX_MAX_LENGTH && fileSuffix.startsWith(FILE_SUFFIX.SUFFIX_START);
}
/**
* 路径选择器获取支持的文件后缀,只支持获取第一个文件后缀
* @param keyFileSuffixChoices 调用方传入文件后缀列表
*/
export function getKeyFileSuffixChoices(keyFileSuffixChoices: string[]): string {
if (!ArrayUtil.isEmpty(keyFileSuffixChoices)) {
let len = keyFileSuffixChoices.length;
let size = len > SUFFIX_LIST_MAX_LENGTH ? SUFFIX_LIST_MAX_LENGTH : len;
for (let index = 0; index < size; index++) {
const suffixStr = keyFileSuffixChoices[index];
if (typeof suffixStr === 'string') {
const suffixArray = suffixStr.split(FILE_SUFFIX.SUFFIX_SPLIT);
for (let index = 0; index < suffixArray.length; index++) {
const suffix = suffixArray[index];
if (checkFileSuffix(suffix)) {
return suffix;
}
}
}
}
}
return '';
}
/**
* 获取媒体库对象实例的统一接口
*/
@@ -0,0 +1,54 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*/
import ObjectUtil from "./ObjectUtil"
/**
* 字符串工具类
*/
export class ArrayUtil {
public static readonly INDEX_INVALID: number = -1;
/**
* 判断array是否为空
*
* @param collection collection
* @return boolean
*/
public static isEmpty<T>(array: T[]): boolean {
if (ObjectUtil.isNullOrUndefined(array)) {
return true;
}
return array.length === 0;
}
/**
* 判断array是否包含item
*
* @param array
* @param item
*/
public static contains<T>(array: T[], item: T): boolean {
if (this.isEmpty(array) || ObjectUtil.isNullOrUndefined(item)) {
return false;
}
return array.indexOf(item) !== this.INDEX_INVALID;
}
/**
* 查找 array 中最大值
* @param array
*/
public static max(array: number[]): number {
return Math.max.apply(null, array);
}
/**
* 查找 array 中最小值
* @param array
*/
public static min(array: number[]): number {
return Math.min.apply(null, array);
}
}
@@ -18,16 +18,20 @@
*/
import Logger from '../log/Logger'
import { FilesData } from '../../databases/model/FileData'
import { DOCUMENTS_FOLDER } from '../constants/Constant'
import { sortDataByTime, randomId } from './Tools'
import { FOLDER_LEVEL, DOCS_FOLDER, DESKTOP_FOLDER } from '../constants/Constant'
import { sortDataByTime, randomId, sortBaseDataByOrderTime } from './Tools'
import fileAccess from '@ohos.file.fileAccess'
import fileExtensionInfo from '@ohos.file.fileExtensionInfo'
import { FileBase } from '../../databases/model/base/FileBase'
import ObjectUtil from './ObjectUtil'
import { FileUtil } from './FileUtil'
import fs from '@ohos.file.fs'
const TAG = 'FileAccessExec'
namespace FileAccessExec {
// 创建文件夹
// 创建文件夹
export function createFolder(sourceUri: string, folderName: string): any {
return new Promise((resolve, reject) => {
try {
@@ -65,26 +69,6 @@ namespace FileAccessExec {
})
}
// 打开文件
export function openFile(sourceUri: string): any {
return new Promise((resolve, reject) => {
try {
// - 0o0:只读打开 0o1:只写打开 0o2:读写打开
globalThis.fileAcsHelper.openFile(sourceUri, 0o2, (ret, data) => {
if (ret && ret.code !== '0') {
reject(ret)
Logger.e(TAG, 'openFile fail:' + JSON.stringify(ret))
} else {
resolve(data)
}
})
} catch (error) {
reject(error)
Logger.e(TAG, 'openFile error occurred:' + error)
}
})
}
export function getFileData(): any {
let folderList = new Array<FilesData>()
let fileList = new Array<FilesData>()
@@ -100,24 +84,26 @@ namespace FileAccessExec {
}))
}
})
return { folderList, fileList }
}
export function getFileByCurIterator(fileInfo: fileAccess.FileInfo, isRoot: boolean = false): Array<FilesData> {
let fileArr = []
let fileIterator = fileInfo.listFile()
let documentsFolder: fileAccess.FileInfo = null
if (!fileIterator) {
return
}
let result = fileIterator.next()
let isDone = result.done
while (!isDone) {
try {
const {fileName, uri, mode, size, mtime, mimeType} = result.value
export function getFileByCurIterator(fileInfo: fileAccess.FileInfo | fileAccess.RootInfo): Array<FilesData> {
let fileList: Array<FilesData> = []
try {
let fileIterator = fileInfo.listFile()
if (!fileIterator) {
Logger.w(TAG, 'getFileByCurIterator fail, fileIterator is null')
return fileList
}
let result = fileIterator.next()
let isDone = result.done
while (!isDone) {
const {fileName, relativePath,uri, mode, size, mtime, mimeType} = result.value
let tempFile = new FilesData({
id: randomId(),
fileName,
relativePath,
uri,
mode,
size,
@@ -125,72 +111,71 @@ namespace FileAccessExec {
mimeType,
fileIterator: result.value
})
// 根目录下不显示Documents文件夹
if (isRoot && result.value.fileName === DOCUMENTS_FOLDER) {
globalThis.documentInfo = tempFile
documentsFolder = result.value
} else {
fileArr.push(tempFile)
}
fileList.push(tempFile)
result = fileIterator.next()
isDone = result.done
}
fileList = sortDataByTime(fileList)
} catch (error) {
fileList = []
Logger.e(TAG, 'getFileByCurIterator fail, error:' + JSON.stringify(error) + error)
}
return fileList
}
export function getPathPickSubFiles(fileInfo: fileAccess.FileInfo, defaultPathPick: string, level: number): Array<FileBase> {
let fileArr: Array<FileBase> = [];
let fileIterator = fileInfo.listFile();
if (!fileIterator) {
return fileArr;
}
let result = fileIterator.next();
let isDone = result.done;
while (!isDone) {
try {
let fileInfo: fileAccess.FileInfo = result.value;
if (!ObjectUtil.isNullOrUndefined(fileInfo)) {
let tempFile = new FileBase(result.value, false);
if (tempFile.isFolder) {
if (FileUtil.hasSubFolder(defaultPathPick, tempFile.currentDir) && level <= FOLDER_LEVEL.MAX_LEVEL) {
tempFile.subList = getPathPickSubFiles(fileInfo, defaultPathPick, level + 1);
}
}
// 根目录下不显示Documents文件夹
fileArr.push(tempFile);
}
result = fileIterator.next();
isDone = result.done;
} catch (e) {
Logger.e(TAG, 'getFileByCurIterator error: ' + e.toString())
isDone = true
Logger.e(TAG, 'getSubFileByIterator error: ' + e.toString());
isDone = true;
}
}
// 处理Documents目录
if (documentsFolder) {
// 获取Documents目录下的文件列表
const documentFileList = getFileByCurIterator(documentsFolder)
// 将Documents目录下的文件合并入根目录
fileArr = fileArr.concat(documentFileList)
} else if (isRoot) {
Logger.e(TAG, 'not found Documents folder in root path')
}
fileArr = sortDataByTime(fileArr)
return fileArr
fileArr = sortBaseDataByOrderTime(fileArr, true);
return fileArr;
}
export function getRootFolder(): Array<FilesData> {
let fileList: Array<FilesData> = []
if (!globalThis.rootInfoArr) {
Logger.e(TAG, 'globalThis.rootInfoArr is null ')
return []
Logger.e(TAG, 'getRootFolder fail, rootInfoArr is null')
return fileList
}
// 过滤本地磁盘
const rootInfo: fileAccess.RootInfo = globalThis.rootInfoArr.find(item => item.deviceType === fileExtensionInfo.DeviceType.DEVICE_LOCAL_DISK)
if (!rootInfo) {
Logger.e(TAG, 'getRootFolder rootInfo is null ')
return []
}
const fileIterator: fileAccess.FileIterator = rootInfo.listFile()
if (!fileIterator) {
Logger.e(TAG, 'getRootFolder fileIterator is null ')
return []
}
let isDone = false
let tempFileIterator = null
while (!isDone) {
const nextFileRes: {
value: fileAccess.FileInfo,
done: boolean
} = fileIterator.next()
const nextFileName = nextFileRes.value.fileName
if (nextFileName.indexOf('FILE') !== -1) {
tempFileIterator = nextFileRes.value
isDone = true
try {
const rootFolder: fileAccess.RootInfo = globalThis.rootInfoArr.find((item: fileAccess.RootInfo) => item.deviceType === fileExtensionInfo.DeviceType.DEVICE_LOCAL_DISK)
if (rootFolder) {
globalThis.documentInfo = rootFolder
fileList = getFileByCurIterator(rootFolder)
fileList = fileList.filter(item => item.fileName !== DESKTOP_FOLDER)
} else {
isDone = nextFileRes.done
Logger.e(TAG, 'rootFolder is null')
}
} catch (error) {
fileList = []
Logger.e(TAG, 'getRootFolder fail, error:' + JSON.stringify(error) + error)
}
if (!tempFileIterator) {
Logger.e(TAG, 'getRootFolder tempFileIterator is null ')
return []
}
return getFileByCurIterator(tempFileIterator, true)
return fileList
}
}
@@ -551,6 +551,28 @@ export class FileMimeTypeUtil {
return mimeType
}
public static getFileSuffix(fileName: string): String {
const unKnown: string = '';
if (StringUtil.isEmpty(fileName)) {
return unKnown;
}
const splitList = fileName.split('.')
if (splitList.length < 2) {
return unKnown;
}
let suffix = splitList[splitList.length-1].toUpperCase()
// 判断DLP加密文件
if (suffix === FileMimeTypeUtil.SUFFIX_DLP) {
if (splitList.length === 2) {
return unKnown;
}
suffix = splitList[splitList.length-2].toUpperCase()
}
return suffix
}
public static getFileTypeOrder(fileName: string): number {
const mimeType = this.getFileMimeType(fileName)
return mimeType.getFileTypeSort()
@@ -15,6 +15,9 @@
import { toast } from '../../base/utils/Common'
import { FileMimeTypeUtil } from '../../base/utils/FileMimeTypeUtil'
import { FILE_SUFFIX, SELECT_MODE } from '../constants/Constant';
import Logger from '../log/Logger';
import ObjectUtil from './ObjectUtil';
/**
* 文件选择器文件状态
@@ -28,21 +31,78 @@ export function pickerStatus(item, checkedNum) {
// 选择是否超限
exceedLimit: globalThis.filePickerViewFlag && (checkedNum >= globalThis.filePickNum && !item.isChecked),
// 选择类型是否不匹配
differentTypes: !checkFileSelectable(item.fileName)
differentTypes: !checkFileSelectable(item)
}
}
/**
* 根据文件后缀判断文件是否可选
* @param fileName
* @param item
*/
function checkFileSelectable(fileName){
if (!fileName) {
return false
}
function checkFileSelectable(item): boolean {
// 非文件选择器场景
if (!globalThis.filePickerViewFlag) {
return true
return true;
}
// selectMode检查
let selectMode: number = globalThis.keySelectMode;
let isFolder = false;
if (ObjectUtil.hasKey(item, 'isFolder')) {
isFolder = item.isFolder;
}
// 文件夹模式,直接返回
if(selectMode === SELECT_MODE.FOLDER){
return isFolder;
}
if (isFolder) {
// 混选模式下,文件夹直接返回
if (selectMode === SELECT_MODE.MIX) {
return true;
}
// 文件模式下,文件夹直接返回false
return false;
}
// 后缀检查
let keyFileSuffixFilter: Array<string> = globalThis.keyFileSuffixFilter;
if (Array.isArray(keyFileSuffixFilter) && keyFileSuffixFilter.length > 0) {
return checkFileSuffix(item.fileName, keyFileSuffixFilter);
}
// mimeType检查
return checkFileMimetype(item.fileName);
}
/**
* 校验选中的文件后缀
*
* @param fileName 文件名称
* @param keyFileSuffixFilter 指定后缀
* @return 如果文件后缀满足三方指定,则返回true
*/
function checkFileSuffix(fileName: string, keyFileSuffixFilter: Array<string>): boolean {
if (keyFileSuffixFilter) {
if (fileName) {
const suffix = FILE_SUFFIX.SUFFIX_START + FileMimeTypeUtil.getFileSuffix(fileName);
if (keyFileSuffixFilter.includes(suffix)) {
return true;
}
}
return false;
}
return true;
}
/**
* 校验选中的文件mimetype
*
* @param fileName 文件名称
* @return 条件满足返回true
*/
function checkFileMimetype(fileName: string): boolean {
if (!fileName) {
return false
}
let keyPickTypeList: Array<string> = globalThis.keyPickTypeList
// 输入的类型全转换成小写,避免大小敏感问题
+329
View File
@@ -0,0 +1,329 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*/
import fileExtensionInfo from '@ohos.file.fileExtensionInfo';
import fileAccess from '@ohos.file.fileAccess';
import ObjectUtil from './ObjectUtil';
import Logger from '../log/Logger';
import StringUtil from './StringUtil';
import { FILENAME_MAX_LENGTH,
RENAME_CONNECT_CHARACTER } from '../constants/Constant';
import MediaLibrary from '@ohos.multimedia.mediaLibrary';
const TAG = 'FileUtil';
export class FileUtil {
/**
* uri 格式开头
*/
static readonly URI_START = 'file://';
/**
* 根据fileAccess.FileInfo中的mode匹配是否是文件夹
* @param mode number
* @returns boolean
*/
public static isFolder(mode: number): boolean {
return (mode & fileExtensionInfo.DocumentFlag.REPRESENTS_DIR) === fileExtensionInfo.DocumentFlag.REPRESENTS_DIR;
}
/**
* 计算文件夹子文件个数
* @param fileIterator fileAccess.FileIterator
* @returns number
*/
public static getChildCountOfFolder(fileIterator: fileAccess.FileIterator): number {
let count = 0;
if (ObjectUtil.isNullOrUndefined(fileIterator)) {
return count;
}
let isDone: boolean = false;
while (!isDone) {
let currItem = fileIterator.next();
isDone = currItem.done;
if (isDone) {
break;
}
count++;
}
return count;
}
/**
* 获取文件信息
* @param uri 文件uri
* @param fileAccessHelper fileAccess.FileAccessHelper
* @returns fileAccess.FileInfo
*/
public static async getFileInfoByUri(uri: string, fileAccessHelper: fileAccess.FileAccessHelper): Promise<fileAccess.FileInfo> {
try {
return await fileAccessHelper.getFileInfoFromUri(uri);
} catch (err) {
Logger.e(TAG, 'getFileInfoByUri err: ' + JSON.stringify(err));
}
return null;
}
/**
* 获取文件信息
* @param relativePath 文件relativePath
* @param fileAccessHelper fileAccess.FileAccessHelper
* @returns fileAccess.FileInfo
*/
public static async getFileInfoByRelativePath(relativePath: string, fileAccessHelper: fileAccess.FileAccessHelper): Promise<fileAccess.FileInfo> {
try {
return await fileAccessHelper.getFileInfoFromRelativePath(relativePath);
} catch (err) {
Logger.e(TAG, 'getFileInfoByRelativePath err: ' + JSON.stringify(err));
}
return null;
}
/**
* 根据uri获取文件夹子文件列表Iterator
* @param uri
* @param fileAccessHelper
* @returns FileIterator
*/
public static async getFileIteratorByUri(uri: string, fileAccessHelper: fileAccess.FileAccessHelper): Promise<fileAccess.FileIterator> {
try {
let fileInfo = await fileAccessHelper.getFileInfoFromUri(uri);
return fileInfo.listFile();
} catch (err) {
Logger.e(TAG, 'getFileIteratorByUri err: ' + JSON.stringify(err));
}
return null;
}
public static getFileAccessHelper(context, wants): fileAccess.FileAccessHelper {
try {
return fileAccess.createFileAccessHelper(context, wants);
} catch (err) {
Logger.i(TAG, 'getFileAccessHelper err: ' + JSON.stringify(err));
}
return null;
}
public static async getFileAccessHelperAsync(context): Promise<fileAccess.FileAccessHelper> {
try {
let wants = await fileAccess.getFileAccessAbilityInfo();
return fileAccess.createFileAccessHelper(context, wants);
} catch (err) {
Logger.i(TAG, 'getFileAccessHelperAsync err: ' + JSON.stringify(err));
}
return null;
}
public static getParentRelativePath(relativePath: string): string {
let curPath = relativePath;
if (StringUtil.isEmpty(relativePath)) {
return '';
}
let index: number = curPath.lastIndexOf('/');
// 去掉最后一个'/'
if (index === curPath.length - 1) {
curPath = curPath.substr(0, index);
}
index = curPath.lastIndexOf('/');
if (index <= 0) {
return '';
}
return curPath.substr(0, index + 1);
}
public static getUsageHabitsKey(prefix: string, suffix: string): string {
return prefix + suffix.charAt(0).toLocaleUpperCase() + suffix.substring(1);
}
/**
* 是否是uri路径
* @param path 路径
* @returns 结果
*/
public static isUriPath(path: string): boolean {
if (ObjectUtil.isNullOrUndefined(path)) {
return false;
}
return path.startsWith(this.URI_START);
}
/**
* 从目录下获取某个文件名的文件
* @param foldrUri 目录uri
* @param fileName 文件名
* return 结果
*/
public static async getFileFromFolder(foldrUri: string, fileName, fileAccessHelper: fileAccess.FileAccessHelper): Promise<fileAccess.FileInfo> {
// 先将目录的信息查询出来
let fileInfo: fileAccess.FileInfo = await this.getFileInfoByUri(foldrUri, fileAccessHelper);
if (ObjectUtil.isNullOrUndefined(fileInfo)) {
return null;
}
// 构建目标目录下的同名文件的相对路径
const destFileRelativePath = fileInfo.relativePath + fileInfo.fileName + '/' + fileName;
// 根据相对路径查询相应的文件
return await this.getFileInfoByRelativePath(destFileRelativePath, fileAccessHelper);
}
/**
* 根据FileInfo获取当前文件的文件夹
*
* @param fileInfo 文件对象
* @returns 返回当前文件的文件夹
*/
public static getCurrentFolderByFileInfo(fileInfo: fileAccess.FileInfo): string {
if (fileInfo !== null) {
let path = fileInfo.relativePath;
return FileUtil.getCurrentDir(path, FileUtil.isFolder(fileInfo.mode));
}
return "";
}
public static async createFolder(fileAccessHelper: fileAccess.FileAccessHelper, parentUri: string, name: string): Promise<{code, uri}> {
let uri: string = '';
let code: any;
try {
uri = await fileAccessHelper.mkDir(parentUri, name);
} catch (error) {
code = error.code;
Logger.e(TAG, 'createFolder error occurred:' + error.code + ', ' + error.message);
}
return {code: code, uri: uri};
}
public static async hardDelete(uri: string, mediaLibrary: MediaLibrary.MediaLibrary): Promise<boolean> {
if (ObjectUtil.isNullOrUndefined(mediaLibrary)) {
return false;
}
try {
await mediaLibrary.deleteAsset(uri);
return true;
} catch (e) {
Logger.e(TAG, 'hardDelete error: ' + JSON.stringify(e));
}
return false;
}
/**
* 重命名
* @param fileAccessHelper FileAccessHelper
* @param oldUri oldUri
* @param newName newName
* @returns {err, uri}
*/
public static async rename(fileAccessHelper: fileAccess.FileAccessHelper, oldUri: string, newName: string): Promise<{err, uri}> {
let uri: string = '';
let err: any;
try {
uri = await fileAccessHelper.rename(oldUri, newName);
} catch (error) {
err = {code: error.code, message: error.message};
Logger.e(TAG, 'rename error occurred:' + error.code + ', ' + error.message);
}
return {err: err, uri: uri};
}
public static async createFile(fileAccessHelper: fileAccess.FileAccessHelper, parentUri: string, fileName: string): Promise<{err, uri}> {
let retUri: string = '';
let err: any;
try {
Logger.i(TAG, 'createFile ' + fileAccessHelper + '; ' + parentUri + " ; " + fileName);
retUri = await fileAccessHelper.createFile(parentUri, fileName);
} catch (e) {
Logger.e(TAG, 'createFile error: ' + e.code + ', ' + e.message);
err = {code: e.code, message: e.message};
}
return {err: err, uri: retUri};
}
public static hasSubFolder(loadPath: string, curFolderPath: string): boolean {
if (!StringUtil.isEmpty(loadPath)) {
if (!StringUtil.isEmpty(curFolderPath)) {
loadPath = FileUtil.getPathWithFileSplit(loadPath);
curFolderPath = FileUtil.getPathWithFileSplit(curFolderPath);
if (loadPath.startsWith(curFolderPath)) {
return true;
}
}
}
return false;
}
public static getPathWithFileSplit(path: string): string {
let fileSplit: string = '/';
if (path && !path.endsWith(fileSplit)) {
path = path + fileSplit;
}
return path;
}
public static loadSubFinish(loadPath: string, curFolderPath: string, maxLevel: number): boolean {
let fileSplit: string = '/';
if (!StringUtil.isEmpty(loadPath)) {
if (!loadPath.endsWith(fileSplit)) {
loadPath = loadPath + fileSplit;
}
let folders = curFolderPath.split(fileSplit);
if ((curFolderPath + fileSplit) === loadPath || folders.length >= maxLevel) {
return true;
}
}
return false;
}
public static renameFile(fileName: string, renameCount: number, suffix: string): string {
if (ObjectUtil.isNullOrUndefined(fileName)) {
return fileName;
}
let newName = fileName;
if (renameCount > 0) {
newName = fileName + RENAME_CONNECT_CHARACTER + renameCount;
let strLen = newName.length + suffix.length;
// 字符长度大于最大长度
if (strLen > FILENAME_MAX_LENGTH) {
// 计算需要裁剪的长度
let subLen = strLen - FILENAME_MAX_LENGTH + 1;
newName = fileName.substring(0, fileName.length - subLen) + RENAME_CONNECT_CHARACTER + renameCount;
}
}
return newName + suffix;
}
public static getFileNameReName(fileName: string): string[] {
if (StringUtil.isEmpty(fileName)) {
return null;
}
let index = fileName.lastIndexOf(RENAME_CONNECT_CHARACTER);
if (index === -1) {
return null;
}
let str = fileName.substring(index + 1, fileName.length);
let name = fileName.substring(0, index);
return [name, str];
}
public static getCurrentDir(path: string, isFolder: boolean): string {
if (isFolder) {
return path;
}
if (path) {
let index: number = path.lastIndexOf('/');
let len: number = path.length;
if (len > 1 && index > 1) {
return path.substring(0, index);
}
}
return path;
}
public static getUriPath(path: string): string {
if (path && FileUtil.isUriPath(path)) {
return path;
}
return null;
}
}
@@ -0,0 +1,52 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*/
/**
* Object工具类
*/
namespace ObjectUtil {
/**
* 判断是否为null
*/
export function isNull(obj: any): boolean {
return obj === null;
}
/**
* 判断是否为undefined
* @param obj
*/
export function isUndefined(obj: any): boolean {
return obj === undefined;
}
/**
* 判断是否为null 或者 undefined
* @param obj
*/
export function isNullOrUndefined(obj: any): boolean {
return isNull(obj) || isUndefined(obj);
}
/**
* 返回string,如果是null or undefined返回defaultValue
*/
export function toString(obj: any, defaultValue: string = ''): string {
if (this.isNullOrUndefined(obj)) {
return defaultValue;
} else {
return obj.toString();
}
}
/**
* 判断对象中是否有某个属性
* @param obj 校验对象
* @param key 校验属性
*/
export function hasKey(obj: object, key: string): boolean {
return Object.prototype.hasOwnProperty.call(obj, key);
}
}
export default ObjectUtil;
@@ -0,0 +1,29 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2015-2022. All rights reserved.
*/
import dataPreferences from '@ohos.data.preferences'
import Logger from '../log/Logger'
const TAG = 'PreferencesUtil'
export const getPreferences = (preferenceName: string) => {
return dataPreferences.getPreferences(globalThis.abilityContext, preferenceName)
}
export const deletePreferences = (preferenceName: string) => {
return dataPreferences.deletePreferences(globalThis.abilityContext, preferenceName)
}
export const removePreferencesFromCache = (preferenceName: string) => {
return dataPreferences.removePreferencesFromCache(globalThis.abilityContext, preferenceName)
}
export const setPreferencesValue = (name: string, key: string, newVal) => {
return getPreferences(name).then(async preferences => {
await preferences.put(key, newVal)
await preferences.flush()
}).catch(err => {
Logger.e(TAG, 'setPreferencesValue error: ' + JSON.stringify(err))
})
}
+15 -34
View File
@@ -18,6 +18,7 @@ import LanguageUtil from './LanguageUtil'
import { FileMimeTypeUtil } from './FileMimeTypeUtil'
import { MimeType } from '../../databases/model/MimeType'
import Logger from '../log/Logger'
import { FileBase } from '../../databases/model/base/FileBase'
const TAG = 'Tools'
@@ -148,40 +149,6 @@ function compareStr(str1: string, str2: string) {
return str2.localeCompare(str1, language)
}
/**
* @description 文件夹名/文件名自动+1
* @param fileItems: 当前列表/目标列表
* @param filename: 需要+1的字符串
* @param name: filename如果有后缀
*/
export const createName = (fileItems: any[], filename: string, name?: string): string => {
try {
const fileNameList = fileItems.map(({ fileName }) => fileName)
name = name ?? ''
let copyReg = /[()]*/g // 复制文件重名时格式化文件名
let lastNum = /([0-9]*)$/ // 获取末尾数字
while (fileNameList.includes(name ? filename + ' ' + name : filename)) {
if (name) {
filename = filename.replace(copyReg, '').replace(lastNum, res => {
res = res < '1' ? '1' : res
const len = res.length
const num = '(' + (+res + 1) + ')'
return num.padStart(len, '0')
})
} else {
filename = filename.replace(lastNum, res => {
const len = res.length
const num = +res + 1 + ''
return num.padStart(len, '0')
})
}
}
return filename
} catch (err) {
return filename
}
}
export const gridName = (fileName) => {
// 文件名超长是中间部分'...'显示
const MAX_LENGTH = 11
@@ -203,4 +170,18 @@ export const isDlpFile = (value): boolean => {
return true
}
return false
}
export const sortBaseDataByOrderTime = (dataList: Array<FileBase>, isDesc: boolean = false) => {
// 规避@State修饰的数组变量执行sort方法不生效问题
const fileList = dataList.filter(item => item);
return fileList.sort((a, b) => {
if (b.modifyTime !== a.modifyTime) {
return isDesc ? b.modifyTime - a.modifyTime : a.modifyTime - b.modifyTime;
} else {
const language = LanguageUtil.getSystemLanguage();
return isDesc ? b.fileName.localeCompare(a.fileName, language) : a.fileName.localeCompare(b.fileName, language);
}
})
}
@@ -13,24 +13,22 @@
* limitations under the License.
*/
export const MILLISECOND = {
ONE_MILLISECOND: 1,
ONE_SECOND: 1000,
ONE_MINUTE: 60 * 1000,
ONE_HOUR: 60 * 60 * 1000,
ONE_DAY: 24 * 60 * 60 * 1000,
ONE_MONTH: 30 * 24 * 60 * 60 * 1000
}
import window from '@ohos.window';
import Logger from '../log/Logger';
import ObjectUtil from './ObjectUtil';
export const BYTE = {
ONE_KB: 1024,
ONE_MB: 1024 * 1024,
ONE_GB: 1024 * 1024 * 1024,
ONE_TB: 1024 * 1024 * 1024 * 1024
}
const TAG = 'UiUtil';
export const FILENAME_REGEXP = /^[^\\/:*?<>\"|]+$/
export class UiUtil {
export const FILENAME_MAX_LENGTH = 250
export const DOCUMENTS_FOLDER = 'Documents'
public static setWindowBackground(color: string): void {
let windowClass: window.Window = globalThis.windowClass;
if (!ObjectUtil.isNullOrUndefined(windowClass)) {
try {
windowClass.setWindowBackgroundColor(color);
} catch (err) {
Logger.e(TAG, 'setWindowBackgroundColor error: ' + JSON.stringify(err));
}
}
}
}
@@ -22,6 +22,8 @@ import { MimeType } from './MimeType'
import { BasicDataSource } from './BasicDataSource'
import fileExtensionInfo from '@ohos.file.fileExtensionInfo'
import AbilityCommonUtil from '../../base/utils/AbilityCommonUtil'
import { FileUtil } from '../../base/utils/FileUtil'
import { ArrayUtil } from '../../base/utils/ArrayUtil'
export class BreadData {
title: string
@@ -133,9 +135,14 @@ export class FilesData {
isFolder: boolean = false
duration: number
mimeTypeObj: MimeType
subFolderList: Array<FilesData>;
subFileList: Array<FilesData>;
layer:number;
autoShow: boolean = false;
currentDir?: string;
constructor(obj) {
this.isFolder = !!(obj.mode & fileExtensionInfo.DocumentFlag.REPRESENTS_DIR)
this.isFolder = obj.isFolder || FileUtil.isFolder(obj.mode);
this.id = obj.id || randomId()
this.uri = obj.uri || ''
this.deviceId = obj.deviceId || ''
@@ -148,7 +155,7 @@ export class FilesData {
this.mtime = obj.mtime * MILLISECOND.ONE_SECOND || 0
this.mimeType = obj.mimeType || ''
this.isChecked = false
this.path = obj.path || ''
this.path = obj.path || obj.relativePath || ''
this.sub = obj.sub || 0
this.scale = obj.scale || 1
this.angle = obj.angle || 0
@@ -163,11 +170,12 @@ export class FilesData {
this.gridIcon = this.mimeTypeObj.getGridResID()
this.localGridIcon = this.mimeTypeObj.getLocalGridResID()
if (this.mimeTypeObj.isMedia()) {
this.thumbUri = `${this.uri}/thumbnail/${THUMBNAIL_SIZE.WIDTH}/${THUMBNAIL_SIZE.HEIGHT}`
this.thumbUri = this.uri
}
if (this.isFolder) {
this.sub = getSubFileNum(this.fileIterator)
}
this.currentDir = FileUtil.getCurrentDir(this.path, this.isFolder);
}
setFileName(fileName: string): void {
@@ -184,6 +192,49 @@ export class FilesData {
pickFile(): void {
AbilityCommonUtil.terminateFilePicker([this.uri], [this.fileName])
}
setSubFolderList(subFolderList: Array<FilesData>) {
this.subFolderList = subFolderList;
}
setSubList(subList: Array<FilesData>) {
if (!ArrayUtil.isEmpty(subList)) {
let folderList = new Array<FilesData>();
let fileList = new Array<FilesData>();
for (let i = 0; i < subList.length; i++) {
let fileData: FilesData = subList[i];
if (fileData.isFolder) {
folderList.push(fileData);
} else {
fileList.push(fileData);
}
}
this.subFolderList = folderList;
this.subFileList = fileList;
}
}
hasSubFolderList(): boolean {
if (ArrayUtil.isEmpty(this.subFolderList)) {
return false;
}
return this.subFolderList.length > 0;
}
getSubFolderList(): Array<FilesData> {
return this.subFolderList;
}
setLayer(layer: number) {
this.layer = layer;
}
getLayer(): number {
if (this.layer) {
return this.layer;
}
return 1;
}
}
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2021-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fileAccess from '@ohos.file.fileAccess';
import { MILLISECOND } from '../../../base/constants/Constant';
import { FileUtil } from '../../../base/utils/FileUtil';
import ObjectUtil from '../../../base/utils/ObjectUtil';
import Logger from '../../../base/log/Logger';
const TAG: string = 'FileBase';
export class FileBase {
uri: string;
relativePath: string;
fileName: string;
mode: number;
isFolder: boolean;
fileSize: number;
modifyTime: number;
mimeType: string;
childCount: number;
firstUri: string;
subList: Array<FileBase>;
currentDir?: string;
constructor(fileInfo: fileAccess.FileInfo, needChildCount: boolean = true) {
if (ObjectUtil.isNullOrUndefined(fileInfo)) {
return;
}
this.uri = fileInfo.uri;
this.relativePath = fileInfo.relativePath;
this.fileName = fileInfo.fileName;
this.fileSize = fileInfo.size;
this.mode = fileInfo.mode;
this.isFolder = FileUtil.isFolder(this.mode);
this.modifyTime = fileInfo.mtime * MILLISECOND.ONE_SECOND;
if (this.modifyTime <= 0) {
Logger.w(TAG, "The modification time of " + this.fileName + " is " + this.modifyTime);
}
this.mimeType = fileInfo.mimeType;
if (this.isFolder && needChildCount) {
this.childCount = FileUtil.getChildCountOfFolder(fileInfo.listFile());
}
this.currentDir = FileUtil.getCurrentDir(this.relativePath, this.isFolder);
}
}
+26 -12
View File
@@ -17,30 +17,38 @@ import UIAbility from '@ohos.app.ability.UIAbility'
import window from '@ohos.window'
import AbilityCommonUtil from '../base/utils/AbilityCommonUtil'
import Logger from '../base/log/Logger'
import { FileUtil } from '../base/utils/FileUtil'
const TAG = 'MainAbility'
export default class MainAbility extends UIAbility {
onCreate(want, launchParam) {
Logger.i(TAG, 'onCreate')
globalThis.action = want.action
globalThis.abilityContext = this.context
globalThis.startMode = want.parameters.startMode
globalThis.saveFile = want.parameters.saveFile ? [want.parameters.saveFile] : []
Logger.i(TAG, 'globalThis.startMode: ' + globalThis.startMode + ', ' + (globalThis.startMode == 'choose'))
globalThis.action = want.action;
globalThis.abilityContext = this.context;
globalThis.startMode = want.parameters.startMode;
globalThis.saveFile = want.parameters.saveFile ? [want.parameters.saveFile] : [];
Logger.i(TAG, 'globalThis.startMode: ' + globalThis.startMode + ', ' + (globalThis.startMode == 'choose'));
const parameters = want.parameters
if (globalThis.action == 'ohos.want.action.OPEN_FILE' || globalThis.startMode == 'choose') {
// 文件选择器
// 选择文件的类型列表
globalThis.keyPickTypeList = AbilityCommonUtil.getKeyPickTypeList(parameters.key_pick_type, parameters.key_pick_type_list)
globalThis.keyPickTypeList = AbilityCommonUtil.getKeyPickTypeList(parameters.key_pick_type, parameters.key_pick_type_list);
// 选择文件个数
globalThis.filePickNum = AbilityCommonUtil.getPickFileNum(parameters.key_pick_num)
globalThis.filePickNum = AbilityCommonUtil.getPickFileNum(parameters.key_pick_num);
// 文件选择范围:0-本地 1-云盘 不传则默认展示全部路径(未实现)
globalThis.filePickLocation = parameters.key_pick_location
// 选择指定目录下的文件(未实现)
globalThis.keyPickDirPath = parameters.key_pick_dir_path
globalThis.pickerCallerUid = parameters[AbilityCommonUtil.CALLER_UID]
globalThis.filePickerViewFlag = true
globalThis.filePickLocation = parameters.key_pick_location;
// 选择指定目录下的文件
globalThis.keyFileDefaultPickPath = FileUtil.getUriPath(parameters.key_pick_dir_path);
// 选择文件模式,默认只支持文件选择
globalThis.keySelectMode = AbilityCommonUtil.getKeySelectMode(parameters.key_select_mode);
// 指定文件后缀,string[]
if (parameters.key_file_suffix_filter instanceof Array) {
globalThis.keyFileSuffixFilter = AbilityCommonUtil.getKeyFileSuffixFilter(parameters.key_file_suffix_filter);
}
globalThis.pickerCallerUid = parameters[AbilityCommonUtil.CALLER_UID];
globalThis.pickerCallerBundleName = parameters[AbilityCommonUtil.CALLER_BUNDLE_NAME]
globalThis.filePickerViewFlag = true;
} else {
// 路径选择器
globalThis.pathAbilityContext = this.context
@@ -50,7 +58,13 @@ export default class MainAbility extends UIAbility {
globalThis.keyPickFileLocation = parameters.key_pick_file_location
// 保存云盘文件到本地时云盘文件的uri列表(未实现)
globalThis.keyPickFilePaths = parameters.key_pick_file_paths
globalThis.keyPathDefaultPickDir = FileUtil.getUriPath(parameters.key_pick_dir_path);
// 指定文件后缀,只获取第一个有效的后缀
if (parameters.key_file_suffix_choices instanceof Array) {
globalThis.keyFileSuffixChoices = AbilityCommonUtil.getKeyFileSuffixChoices(parameters.key_file_suffix_choices);
}
globalThis.pathCallerUid = parameters[AbilityCommonUtil.CALLER_UID]
globalThis.pathCallerBundleName = parameters[AbilityCommonUtil.CALLER_BUNDLE_NAME]
}
Logger.i(TAG, ' onCreate, parameters: ' + JSON.stringify(want.parameters))
}
+155 -16
View File
@@ -14,12 +14,18 @@
*/
import { fileTree } from './component/dialog/FileMoveDialog'
import FileAccessExec from '../base/utils/FileAccessExec'
import Logger from '../base/log/Logger'
import ErrorCodeConst from '../base//constants/ErrorCodeConst'
import { toast, setSystemBar, setImmersion } from '../base/utils/Common'
import AbilityCommonUtil from '../base/utils/AbilityCommonUtil'
import { SYSTEM_BAR_COLOR } from '../base/constants/UiConstant'
import StringUtil from '../base/utils/StringUtil'
import { FileUtil } from '../base/utils/FileUtil'
import ObjectUtil from '../base/utils/ObjectUtil'
import MediaLibrary from '@ohos.multimedia.mediaLibrary';
import fileAccess from '@ohos.file.fileAccess'
import { ArrayUtil } from '../base/utils/ArrayUtil'
import { UiUtil } from '../base/utils/UiUtil'
const TAG = 'PathSelector'
@@ -27,9 +33,10 @@ const TAG = 'PathSelector'
@Component
struct PathSelector {
@State createResultType:number = ErrorCodeConst.PICKER.NORMAL;
aboutToAppear(){
setImmersion(false)
setSystemBar(SYSTEM_BAR_COLOR.WHITE, SYSTEM_BAR_COLOR.WHITE, SYSTEM_BAR_COLOR.BLACK, SYSTEM_BAR_COLOR.BLACK)
UiUtil.setWindowBackground(SYSTEM_BAR_COLOR.LIGHT_GRAY);
}
async saveFileCallback(res): Promise<void> {
@@ -42,15 +49,22 @@ struct PathSelector {
if (fileNameList.length <= 1) {
fileNameList = [res.fileName]
}
this.createFile(res.selectUri, fileNameList).then((createdFileList) => {
this.saveFiles(res.selectUri, fileNameList).then((createdFileList) => {
AbilityCommonUtil.terminatePathPicker(createdFileList)
}).catch((err) => {
let errorMessage = ''
let errorCode = 0
Logger.e(TAG, JSON.stringify(err));
if (err.code) {
if (err.code === ErrorCodeConst.FILE_ACCESS.FILE_NAME_EXIST) {
errorMessage = 'Same name file already exists'
errorCode = ErrorCodeConst.PICKER.FILE_NAME_EXIST
this.createResultType = errorCode;
const pathName = globalThis.keyPickFileName;
let listLength:number = pathName.length;
if(listLength == 1){
return;
}
} else if (err.code === ErrorCodeConst.FILE_ACCESS.FILE_NAME_INVALID) {
errorMessage = 'Invalid display name'
errorCode = ErrorCodeConst.PICKER.FILE_NAME_INVALID
@@ -69,27 +83,152 @@ struct PathSelector {
}
}
async createFile(folderUri: string, fileNameList: Array<string>): Promise<Array<string>> {
let createdFileUriList = []
/**
* PathPicker保存文件
* @param data SaveFilesParam
*/
async saveFiles(path: string, nameList: Array<string>): Promise<Array<string>> {
return new Promise(async (resolve, reject) => {
let resolveFileName = ''
try {
for (let i = 0; i < fileNameList.length; i++) {
resolveFileName = fileNameList[i]
const newFileUri = await FileAccessExec.createFile(folderUri, resolveFileName)
createdFileUriList.push(newFileUri)
let fileAccessHelper = await FileUtil.getFileAccessHelperAsync(globalThis.abilityContext);
let dirPath = path;
if (StringUtil.isEmpty(dirPath)) {
dirPath = (await FileUtil.getFileInfoByRelativePath('Documents/', fileAccessHelper)).uri;
}
let fileNameArr = nameList;
let successArr: Array<string> = [];
let resultErr: any;
let len: number = fileNameArr.length;
let fileNameList: string[] = [];
if (len > 1) {
fileNameList = await this.getPickPathListFiles(dirPath, fileAccessHelper);
}
Logger.i(TAG, 'saveFiles createName: ' + JSON.stringify(fileNameArr)+" ; ");
Logger.i(TAG, 'saveFiles subList: ' + JSON.stringify(fileNameList)+" ; ");
for (let i = 0; i < len; i++) {
const currName = fileNameArr[i];
let result
if (len === 1) {
result = await FileUtil.createFile(fileAccessHelper, dirPath, currName);
} else {
result = await this.tryRenameFileOperate(fileAccessHelper, currName, dirPath, 0, fileNameList);
}
resolve(createdFileUriList)
} catch (err) {
reject(err)
Logger.e(TAG, `createFile error, fileName: ${resolveFileName}, massage: ${err.message ? err.message : err}`)
if (ObjectUtil.isUndefined(result.err)) {
Logger.i(TAG, "saveFiles createOK: " + result.uri);
successArr.push(result.uri);
continue;
}
Logger.i(TAG, 'saveFiles err: ' + result.err.code);
// 失败
resultErr = { code: result.err.code, message: result.err.message };
let mediaLibrary;
try {
mediaLibrary = MediaLibrary.getMediaLibrary(globalThis.abilityContext);
} catch (error) {
Logger.e(TAG, 'getMediaLibrary fail, error:' + JSON.stringify(error))
}
if (ObjectUtil.isNullOrUndefined(mediaLibrary)) {
break;
}
for (let i = 0; i < successArr.length; i++) {
await FileUtil.hardDelete(successArr[i], mediaLibrary);
}
try {
mediaLibrary.release();
} catch (e) {
Logger.e(TAG, 'mediaLibrary close error')
}
successArr = [];
break;
}
Logger.i(TAG, 'saveFiles end: ' + JSON.stringify(successArr));
if (!ArrayUtil.isEmpty(successArr)) {
resolve(successArr);
} else {
reject(resultErr);
}
})
}
private async getPickPathListFiles(dirUri: string, fileAccessHelper: fileAccess.FileAccessHelper): Promise<string[]> {
let fileInfo: fileAccess.FileInfo = await FileUtil.getFileInfoByUri(dirUri, fileAccessHelper);
if (ObjectUtil.isNullOrUndefined(fileInfo) || !FileUtil.isFolder(fileInfo.mode)) {
return [];
}
return this.getFilesByIterator(fileInfo.listFile());
}
private getFilesByIterator(fileIterator: fileAccess.FileIterator): string[] {
if (ObjectUtil.isNull(fileIterator)) {
return null;
}
let result: Array<string> = new Array();
let isDone = false;
while (!isDone) {
try {
let nextFileInfo = fileIterator.next();
isDone = nextFileInfo.done;
if (isDone) {
break;
}
let currFile = nextFileInfo.value;
if (!FileUtil.isFolder(currFile.mode)) {
result.push(currFile.fileName);
}
} catch (err) {
Logger.e(TAG, 'current File err: ' + JSON.stringify(err) + ', ' + err.toString());
}
}
return result;
}
private async tryRenameFileOperate(fileAccessHelper: fileAccess.FileAccessHelper, fileName: string, dirUri: string, renameCount: number, fileNameList: string[] = []): Promise<{ err,uri }> {
let index = fileName.lastIndexOf('.');
let name = fileName;
let suffix = '';
if (index !== -1) {
suffix = fileName.substring(index, fileName.length);
name = fileName.substring(0, index);
}
let hasReNameCount = FileUtil.getFileNameReName(name);
if (!ObjectUtil.isNullOrUndefined(hasReNameCount)) {
let num = Number(hasReNameCount[1]);
if (!isNaN(num)) {
name = hasReNameCount[0];
renameCount = num;
}
}
let newName = fileName;
while (true) {
newName = FileUtil.renameFile(name, renameCount++, suffix);
let index = this.getIndex(newName, fileNameList);
Logger.i(TAG, "tryRenameFileOperate : " + newName + " ; index = " + index);
if (index === -1) {
const result = await FileUtil.createFile(fileAccessHelper, dirUri, newName);
if (ObjectUtil.isUndefined(result.err)) {
Logger.i(TAG, "tryRenameFileOperate createOK: " + result.uri);
return result;
} else {
Logger.i(TAG, "tryRenameFileOperate createFail: " + JSON.stringify(result) + " ; " + newName);
if (result.err.code === ErrorCodeConst.FILE_ACCESS.FILE_NAME_EXIST) {
fileNameList.push(newName);
} else {
return result;
}
}
}
}
}
private getIndex(fileName: string, fileNameList: string[] = []) {
return fileNameList.findIndex(value => value === fileName);
}
build() {
Row() {
fileTree({
createFileFailType: $createResultType,
moveCallback: (e) => {
this.saveFileCallback(e)
}
@@ -29,6 +29,10 @@ import { getMediaType, getDurationByUri } from '../../../databases/model/FileAss
import Logger from '../../../base/log/Logger'
import multimedia_image from '@ohos.multimedia.image'
import AbilityCommonUtil from '../../../base/utils/AbilityCommonUtil'
import ObjectUtil from '../../../base/utils/ObjectUtil'
import StringUtil from '../../../base/utils/StringUtil'
import { FileUtil } from '../../../base/utils/FileUtil'
import fileAccess from '@ohos.file.fileAccess'
const TAG = 'myPhone'
@@ -89,41 +93,59 @@ struct MyPhone {
}
async getBreadCrumb(data: string): Promise<void> {
if (data.startsWith('Documents/')) {
data = data.replace('Documents/', '')
if (!data) {
data = "";
}
let arr = data.split('/')
arr.pop()
if (arr.length == 0) {
this.getRootListFile()
return
}
const lastItem = arr[arr.length-1]
if (lastItem.indexOf('.') > -1) {
arr.pop()
if (FileUtil.isUriPath(data)) {
let fileHelper = await FileUtil.getFileAccessHelperAsync(globalThis.abilityContext);
// 将uri转换成相对路径
let curFileInfo: fileAccess.FileInfo = await FileUtil.getFileInfoByUri(data, fileHelper);
if (!ObjectUtil.isNullOrUndefined(curFileInfo)) {
data = FileUtil.getCurrentDir(curFileInfo.relativePath, FileUtil.isFolder(curFileInfo.mode));
}
}
data = FileUtil.getPathWithFileSplit(data);
let fileIterator
let fileData
for (let j = 0;j < arr.length; j++) {
const e = arr[j]
if (j === 0) {
let isContinue:boolean = true;
let isRoot: boolean = true;
while (isContinue) {
isContinue = false;
if (!fileIterator) {
fileData = FileAccessExec.getRootFolder()
isRoot = true;
} else {
fileData = FileAccessExec.getFileByCurIterator(fileIterator, false)
fileData = FileAccessExec.getFileByCurIterator(fileIterator)
isRoot = false;
}
if (Array.isArray(fileData)) {
isContinue = true;
for (let i = 0;i < fileData.length; i++) {
if (fileData[i].fileName == e) {
this.direList.push({ title: e, url: fileData[i].uri, fileIterator: fileData[i].fileIterator })
fileIterator = fileData[i].fileIterator
break
let fileName:string = fileData[i].fileName;
let currentDir:string = FileUtil.getPathWithFileSplit(fileData[i].currentDir);
if (data.startsWith(currentDir)) {
if (fileData[i].isFolder) {
this.direList.push({ title: fileName, url: fileData[i].uri, fileIterator: fileData[i].fileIterator })
fileIterator = fileData[i].fileIterator
if (data === currentDir) {
isContinue = false;
} else {
break;
}
}
}
}
}
if (isRoot && !fileIterator) {
isContinue = false;
}
}
if (arr.length >= 1) {
fileData = FileAccessExec.getFileByCurIterator(fileIterator, false)
if (fileIterator) {
fileData = FileAccessExec.getFileByCurIterator(fileIterator)
this.fileListSource.setData(fileData)
} else {
this.getRootListFile();
}
}
@@ -143,7 +165,7 @@ struct MyPhone {
}
getListFile(fileInfo) {
let fileList = FileAccessExec.getFileByCurIterator(fileInfo, false)
let fileList = FileAccessExec.getFileByCurIterator(fileInfo)
this.fileListSource.setData(fileList)
this.getVideoAudioDuration(fileList)
}
@@ -208,12 +230,28 @@ struct MyPhone {
multimedia_image.createPixelMap(new ArrayBuffer(4096), { size: { height: 1, width: 2 } }).then((pixelMap) => {
})
this.setShowLoading(true)
if (!router.getParams()) {
this.getRootListFile()
let pickPath = this.getParams();
if (StringUtil.isEmpty(pickPath)) {
this.getRootListFile();
} else {
this.getBreadCrumb(router.getParams()['path'])
this.getBreadCrumb(pickPath);
}
this.setShowLoading(false)
this.setShowLoading(false);
}
getParams(): string {
let defaultPickPath = globalThis.keyFileDefaultPickPath;
if (!ObjectUtil.isNullOrUndefined(defaultPickPath)) {
return defaultPickPath;
}
let params = router.getParams();
if (!ObjectUtil.isNullOrUndefined(params)) {
defaultPickPath = params['path'];
if (!ObjectUtil.isNullOrUndefined(defaultPickPath)) {
return defaultPickPath;
}
}
return "";
}
onDireListChange(): void {
+188 -45
View File
@@ -17,15 +17,25 @@ import { FilesData } from '../../databases/model/FileData'
import { on } from '../../base/utils/EventBus'
import FileAccessExec from '../../base/utils/FileAccessExec'
import { TREE_LAYER } from '../../base/constants/UiConstant'
import { FOLDER_LEVEL } from '../../base/constants/Constant'
import { FileUtil } from '../../base/utils/FileUtil'
import { ArrayUtil } from '../../base/utils/ArrayUtil'
import { FileBase } from '../../databases/model/base/FileBase'
import Logger from '../../base/log/Logger'
import fileAccess from '@ohos.file.fileAccess'
import ObjectUtil from '../../base/utils/ObjectUtil'
@Styles function pressedStyles() {
.borderRadius($r('app.float.common_borderRadius8'))
.backgroundColor($r('app.color.hicloud_hmos_bg'))
}
const TAG = 'TreeItem';
@Component
export struct TreeItem {
fileItem: FilesData = new FilesData({})
loadPath?: string = '';
isNeedExpand: boolean = false;
@State iconRotate: boolean = false
@State subFolderList: Array<FilesData> = new Array<FilesData>()
@Link chooseItem: FilesData
@@ -35,48 +45,176 @@ export struct TreeItem {
@Link folderList: Array<FilesData>
@State isShowArrow: boolean = true
@Prop layer: number
@State isLoading: boolean = false;
@Link @Watch('clickExpandChange') isClickExpand: boolean;
getSelectItem(path: string): Array<FilesData> {
let folderList = new Array<FilesData>()
let fileList = new Array<FilesData>()
let allFileList = FileAccessExec.getFileByCurIterator(this.fileItem.fileIterator)
allFileList.forEach((item) => {
if (item.isFolder) {
item.path = `${path}/${item.fileName}`
folderList.push(new FilesData({
...item
}))
} else {
fileList.push(new FilesData({
...item
}))
}
})
private changeSelectItem(selectedItem: FilesData, autoShow: boolean) {
if (selectedItem) {
selectedItem.autoShow = autoShow;
this.chooseItem = selectedItem;
this.selectUri = this.chooseItem.uri;
this.selectName = this.chooseItem.fileName;
}
}
private async executeQuery(dirUri: string, defaultExpandPath: string, call: Function) {
this.isLoading = true;
if (!this.isNeedExpand || (this.isNeedExpand && !this.isClickExpand)) {
this.changeSelectItem(this.fileItem, false);
}
let queryRes = await this.getPickPathListFiles(dirUri, defaultExpandPath, this.fileItem.layer);
this.isLoading = false;
let subList: Array<FilesData> = this.fileBaseToFileData(queryRes);
let { folderList, fileList } = this.transfer(subList);
this.fileList = fileList
return folderList
call(folderList);
}
private async getPickPathListFiles(dirUri: string, expandPath: string, level: number): Promise<Array<FileBase>> {
let fileHelper = await FileUtil.getFileAccessHelperAsync(globalThis.abilityContext);
let fileInfo: fileAccess.FileInfo = await FileUtil.getFileInfoByUri(dirUri, fileHelper);
if (ObjectUtil.isNullOrUndefined(fileInfo) || !FileUtil.isFolder(fileInfo.mode)) {
Logger.e(TAG, 'uri is not folder');
return;
}
let queryRes = FileAccessExec.getPathPickSubFiles(fileInfo, expandPath, level);
if (ObjectUtil.isNull(queryRes)) {
Logger.e(TAG, 'files is null');
return;
}
return queryRes;
}
transfer(list: FilesData[]) {
let folderList = new Array<FilesData>();
let fileList = new Array<FilesData>();
if (ArrayUtil.isEmpty(list)) {
return { folderList, fileList };
}
for (let i = 0; i < list.length; i++) {
let fileData = list[i];
if (fileData.isFolder) {
folderList.push(fileData);
} else {
fileList.push(fileData);
}
}
return { folderList, fileList };
}
fileBaseToFileData(list: Array<FileBase>): Array<FilesData> {
let fileArray = new Array<FilesData>();
if (ArrayUtil.isEmpty(list)) {
return fileArray;
}
for (let i = 0; i < list.length; i++) {
let data = list[i];
let fileData = new FilesData([]);
fileData.uri = data.uri;
fileData.fileName = data.fileName;
fileData.isFolder = data.isFolder;
fileData.size = data.fileSize;
fileData.mtime = data.modifyTime;
fileData.path = data.relativePath;
fileData.currentDir = data.currentDir;
if (data.isFolder) {
if (!ArrayUtil.isEmpty(data.subList)) {
fileData.setSubList(this.fileBaseToFileData(data.subList))
}
}
fileArray.push(fileData);
}
return fileArray;
}
/**
* 是否需要展开目录,如果最近保存的目录不为空,需要展开到最近保存的目录
*
* @returns true:需要展开目录
*/
needExpandPath(): boolean {
if (!this.canExpandPath() || this.isClickExpand) {
return false;
}
return FileUtil.hasSubFolder(this.loadPath, this.fileItem.currentDir);
}
clickExpandChange() {
this.isNeedExpand = false;
this.loadPath = '';
}
canExpandPath(): boolean {
return this.layer <= FOLDER_LEVEL.MAX_LEVEL;
}
loadSubFolder(subFolderList: Array<FilesData>) {
this.subFolderList = subFolderList;
this.folderList = this.subFolderList;
this.fileItem.setSubFolderList(subFolderList);
Logger.i(TAG, "loadSubFolder:selectUri = " + this.selectUri +
" ; subFolderListSize = " + this.subFolderList.length +
" ; iconRotate = " + this.iconRotate);
}
aboutToAppear() {
on('fileMkdir', (e) => {
on('fileMkdir', async (e) => {
if (this.selectUri === this.fileItem.uri) {
// 获取当前选中文件夹下的所有子文件
this.subFolderList = this.getSelectItem(this.fileItem.path)
let queryArray = await this.getPickPathListFiles(this.fileItem.uri, "", this.fileItem.layer);
let subList: Array<FilesData> = this.fileBaseToFileData(queryArray);
let { folderList, fileList } = this.transfer(subList);
this.fileList = fileList
// 获取当前选中文件夹下的所有子文件
this.subFolderList = folderList;
this.expandSubFolderCall(folderList);
// 查找刚刚新建的文件夹index
const index = this.subFolderList.findIndex(item => item.fileName === e.mkdirName)
if (index !== -1) {
const index = this.subFolderList.findIndex(item => item.fileName === e.mkdirName);
if (index !== -1 && this.canExpandPath()) {
// 默认选中刚刚新建的文件夹
this.chooseItem = this.subFolderList[index]
this.selectUri = this.subFolderList[index].uri
this.selectName = this.subFolderList[index].fileName
this.iconRotate = true
this.folderList = []
this.fileList = []
} else {
this.folderList = this.subFolderList
this.changeSelectItem(this.subFolderList[index], true);
this.iconRotate = true;
this.fileList = [];
this.folderList = [];
}
}
})
this.fileItem.setLayer(this.layer);
this.isNeedExpand = this.needExpandPath();
if (this.isNeedExpand) {
Logger.i(TAG, "NeedExpand:loadPath = " + this.loadPath +
" ; path = " + this.fileItem.currentDir);
this.clickExpand(false);
}
}
clickExpand(forceLoading: boolean) {
if (!this.isLoading) {
if (this.iconRotate) {
this.iconRotate = !this.iconRotate;
this.changeSelectItem(this.fileItem, false);
this.fileItem.subFileList = null;
this.folderList = this.fileItem.subFolderList;
} else {
if (this.canExpandPath()) {
if (this.fileItem.hasSubFolderList() && !forceLoading) {
this.changeSelectItem(this.fileItem, false);
this.fileList = this.fileItem.subFileList;
this.expandSubFolderCall(this.fileItem.getSubFolderList());
} else {
this.executeQuery(this.fileItem.uri, this.loadPath, this.expandSubFolderCall.bind(this));
}
}
}
}
}
private expandSubFolderCall(subFolderList: Array<FilesData>) {
this.iconRotate = !this.iconRotate;
this.loadSubFolder(subFolderList);
this.isShowArrow = this.subFolderList.length !== 0;
}
build() {
@@ -94,13 +232,21 @@ export struct TreeItem {
.layoutWeight(1)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Image($r('app.media.ic_arrow_right'))
.objectFit(ImageFit.Contain)
.renderMode(ImageRenderMode.Original)
.aspectRatio(1)
.width($r('app.float.common_size15'))
.rotate({ z: 90, angle: this.iconRotate ? 90 : 0 })
.visibility(this.isShowArrow ? Visibility.Visible : Visibility.None)
if (this.isLoading) {
LoadingProgress()
.width($r('app.float.common_size24'))
.height($r('app.float.common_size24'))
.color($r('sys.color.ohos_id_color_text_secondary'))
} else {
Image($r('app.media.ic_arrow_right'))
.objectFit(ImageFit.Contain)
.autoResize(true)
.height($r('app.float.common_size12'))
.width($r('app.float.common_size12'))
.interpolation(ImageInterpolation.Medium)
.rotate({ z: 90, angle: this.iconRotate ? 90 : 0 })
.visibility(this.isShowArrow ? Visibility.Visible : Visibility.None)
}
}
.width('100%')
.padding({
@@ -114,25 +260,22 @@ export struct TreeItem {
pressed: pressedStyles
})
.onClick(() => {
this.iconRotate = !this.iconRotate
this.selectUri = this.fileItem.uri
this.selectName = this.fileItem.fileName
this.chooseItem = this.fileItem
this.subFolderList = this.getSelectItem(this.fileItem.path)
this.folderList = this.subFolderList
this.isShowArrow = this.subFolderList.length !== 0
this.isClickExpand = true;
this.clickExpand(true);
})
if (this.subFolderList.length && this.iconRotate) {
ForEach(this.subFolderList, (item) => {
TreeItem({
fileItem: item,
loadPath: this.loadPath,
chooseItem: $chooseItem,
selectUri: $selectUri,
selectName: $selectName,
layer: this.layer + 1,
folderList: $folderList,
fileList: $fileList
fileList: $fileList,
isClickExpand: $isClickExpand
})
})
}
@@ -17,6 +17,11 @@
.backgroundColor($r('app.color.button_pressStyles'))
}
@Styles function normalStyles () {
.borderRadius($r('app.float.common_borderRadius8'))
.backgroundColor($r('app.color.transparent_color'))
}
@Component
export struct DialogTitle {
title: Resource
@@ -63,7 +68,8 @@ export struct DialogButton {
.layoutWeight(1)
.backgroundColor(this.bgColor)
.stateStyles({
pressed: pressStyles
pressed: pressStyles,
normal: normalStyles
})
.opacity(this.isDisabled ? $r('app.float.common_opacity5') : $r('app.float.common_opacity10'))
.borderRadius($r('app.float.common_borderRadius18'))
@@ -22,6 +22,11 @@ import AbilityCommonUtil from '../../../base//utils/AbilityCommonUtil'
.backgroundColor($r('app.color.hicloud_hmos_bg'))
}
@Styles function normalStyles () {
.borderRadius($r('app.float.common_borderRadius8'))
.backgroundColor($r('app.color.transparent_color'))
}
@Extend(Text) function subtitleStyles(fontSize: Resource) {
.fontSize(fontSize)
.alignSelf(ItemAlign.Start)
@@ -90,6 +95,7 @@ export struct TopBar {
.objectFit(ImageFit.Contain)
.width($r('app.float.common_size24'))
.height($r('app.float.common_size24'))
.interpolation(ImageInterpolation.Medium)
}.padding({
left: $r('app.float.common_padding12'),
right: $r('app.float.common_padding12'),
@@ -97,7 +103,8 @@ export struct TopBar {
bottom: $r('app.float.common_padding10')
})
.stateStyles({
pressed: pressedStyles
pressed: pressedStyles,
normal: normalStyles
})
.onClick(() => {
this.backCallback.call(this)
@@ -132,6 +139,7 @@ export struct TopBar {
.objectFit(ImageFit.Contain)
.width($r('app.float.common_size24'))
.height($r('app.float.common_size24'))
.interpolation(ImageInterpolation.Medium)
}.padding({
left: $r('app.float.common_padding12'),
right: $r('app.float.common_padding12'),
@@ -139,7 +147,8 @@ export struct TopBar {
bottom: $r('app.float.common_padding10')
})
.stateStyles({
pressed: pressedStyles
pressed: pressedStyles,
normal: normalStyles
})
.onClick(() => {
let abilityContext = getContext(this) as context.UIAbilityContext
@@ -158,7 +167,8 @@ export struct TopBar {
bottom: $r('app.float.common_padding10')
})
.stateStyles({
pressed: pressedStyles
pressed: pressedStyles,
normal: normalStyles
})
.onClick(() => {
this.terminate()
@@ -18,11 +18,16 @@
.backgroundColor($r('app.color.hicloud_hmos_bg'))
}
@Styles function normalStyles () {
.borderRadius($r('app.float.common_borderRadius8'))
.backgroundColor($r('app.color.transparent_color'))
}
@Component
export struct TopOperateBar {
public addFolder: () => void // 点击新建的事件回调
// 是否可用
isDisabled: boolean = false
@Prop isDisabled: boolean = false
// 列表或宫格
@Consume isList: boolean
@@ -44,7 +49,8 @@ export struct TopOperateBar {
bottom: $r('app.float.common_padding10')
})
.stateStyles({
pressed: pressedStyles
pressed: pressedStyles,
normal: normalStyles
})
.onClick(() => {
if (this.isDisabled) return
@@ -71,7 +77,8 @@ export struct TopOperateBar {
if (this.isDisabled) return
this.isList = !this.isList
}).stateStyles({
pressed: pressedStyles
pressed: pressedStyles,
normal: normalStyles
})
}
.width('100%')
@@ -16,7 +16,7 @@
import { FilesData } from '../../../databases/model/FileData'
import FileAccessExec from '../../../base/utils/FileAccessExec'
import { toast, isValidFileName } from '../../../base/utils/Common'
import { createName, getResourceString } from '../../../base/utils/Tools'
import { getResourceString } from '../../../base/utils/Tools'
import Logger from '../../../base/log/Logger'
import { DialogTitle, DialogButton, DialogButtonDivider } from '../common/DialogComponent'
@@ -29,12 +29,25 @@ export struct FileMkdirDialog {
cancel: Function
confirm: Function
@State folderName: string = ''
@State errorText: string = ''
@State errorText: Resource = null
fileItems: Array<FilesData>
getCurrentDir: string = ''
aboutToAppear() {
this.folderName = createName(this.fileItems, getResourceString($r('app.string.addFolder')))
this.folderName = this.getNewFolderName()
}
getNewFolderName(): string {
const newFolderText = getResourceString($r('app.string.addFolder'));
const regExp = new RegExp(`^${newFolderText}([ ]{1}[0-9]+)*$`);
const tempFolderList = this.fileItems.filter(item => item.isFolder && regExp.test(item.fileName));
let tempFolderName = newFolderText;
let index = 0;
while (tempFolderList.some(item => item.fileName === tempFolderName)) {
index += 1;
tempFolderName = newFolderText + ' ' + index.toString();
};
return tempFolderName;
}
isSameName() {
@@ -61,7 +74,7 @@ export struct FileMkdirDialog {
})
.onChange((value: string) => {
this.folderName = value
this.errorText = ''
this.errorText = null
})
Divider().vertical(false).strokeWidth(1).color(Color.Gray)
.margin({
@@ -89,9 +102,9 @@ export struct FileMkdirDialog {
isDisabled: !this.folderName.trim(),
click: () => {
if (!isValidFileName(this.folderName)) {
this.errorText = getResourceString($r('app.string.illegal'))
this.errorText = $r('app.string.illegal')
} else if (this.isSameName()) {
this.errorText = getResourceString($r('app.string.sameName'))
this.errorText = $r('app.string.sameName')
} else {
FileAccessExec.createFolder(this.getCurrentDir, this.folderName).then(folderUri => {
this.confirm({
@@ -20,14 +20,26 @@ import { TreeItem } from '../TreeItem'
import { FileMkdirDialog } from './FileMkdirDialog'
import { emit } from '../../../base/utils/EventBus'
import { getResourceString } from '../../../base/utils/Tools'
import { FILENAME_MAX_LENGTH } from '../../../base/constants/Constant'
import { FILENAME_MAX_LENGTH, FILE_MANAGER_PREFERENCES, FOLDER_LEVEL } from '../../../base/constants/Constant'
import StringUtil from '../../../base/utils/StringUtil'
import { setPreferencesValue } from '../../../base/utils/PreferencesUtil'
import { FileUtil } from '../../../base/utils/FileUtil'
import Logger from '../../../base/log/Logger'
import { ArrayUtil } from '../../../base/utils/ArrayUtil'
import ErrorCodeConst from '../../../base/constants/ErrorCodeConst'
@Styles function pressedStyles () {
.borderRadius($r('app.float.common_borderRadius8'))
.backgroundColor($r('app.color.hicloud_hmos_bg'))
}
@Styles function normalStyles() {
.borderRadius($r('app.float.common_borderRadius8'))
.backgroundColor($r('app.color.transparent_color'))
}
const TAG = 'fileTree';
@Component
export struct fileTree {
@State listLength: number = 0
@@ -37,7 +49,7 @@ export struct fileTree {
@State selectUri: string = ''
@State @Watch('nameChange') selectName: string = getResourceString($r('app.string.myPhone'))
public moveCallback: (e) => void
@State chooseItem: FilesData = new FilesData({})
@State @Watch('selectChange') chooseItem: FilesData = new FilesData({})
@State folderList: FilesData[] = new Array<FilesData>()
@State fileList: FilesData[] = new Array<FilesData>()
@State changeTitle: Resource = undefined
@@ -55,6 +67,13 @@ export struct fileTree {
})
@State isSelectRootPath: boolean = true
@State errorText: Resource = undefined
@State isNeedLoadDefaultPath: boolean = false
@State isClickExpand: boolean = false;
@Link @Watch('createFileFailTypeChange') createFileFailType: number;
lastSelectPath: string = AppStorage.Get<string>(FILE_MANAGER_PREFERENCES.lastSelectPath.key);
defaultExpandPath: string = '';
scroller: Scroller = new Scroller();
context: Context = globalThis.abilityContext;
async aboutToAppear() {
toast($r('app.string.select_location'))
@@ -63,12 +82,16 @@ export struct fileTree {
const fileName: string = fileNameList[0]
if (fileName) {
const dotIndex = fileName.lastIndexOf('.')
if (dotIndex > 0) {
this.fileName = fileName.substring(0, dotIndex)
this.suffix = fileName.substring(dotIndex + 1)
let fileSuffix = globalThis.keyFileSuffixChoices;
if (!StringUtil.isEmpty(fileSuffix)) {
this.suffix = fileSuffix;
if (dotIndex > 0) {
this.fileName = fileName.substring(0, dotIndex) + fileSuffix;
} else {
this.fileName = fileName + fileSuffix;
}
} else {
this.fileName = fileName
this.suffix = ''
this.fileName = fileName;
}
}
this.nameChange()
@@ -81,6 +104,7 @@ export struct fileTree {
if (globalThis.documentInfo) {
this.selectUri = globalThis.documentInfo.uri
}
this.loadDefaultExpandPath();
}
fileMkdir(e) {
@@ -112,17 +136,102 @@ export struct fileTree {
}
}
createFileFailTypeChange() {
if (this.createFileFailType === ErrorCodeConst.PICKER.FILE_NAME_EXIST) {
this.errorText = $r('app.string.save_file_has_same_file');
this.createFileFailType = ErrorCodeConst.PICKER.NORMAL;
}
}
selectChange() {
let autoShow: boolean = false;
if (this.chooseItem) {
autoShow = this.chooseItem.autoShow;
this.chooseItem.autoShow = false;
}
if (!this.isClickExpand || autoShow) {
let loadSubFinish = FileUtil.loadSubFinish(this.defaultExpandPath, this.chooseItem.currentDir, FOLDER_LEVEL.MAX_LEVEL - 2);
if (loadSubFinish || autoShow) {
let allData: Array<FilesData> = new Array<FilesData>();
let pos = this.getSelectItemPos(this.rootData, allData);
let itemHeight: number = this.context.resourceManager.getNumber($r('app.float.common_size56'));
let scrollY: number = itemHeight * (pos - 1);
Logger.i(TAG, "selectItemPos = " + pos + ",itemHeight = " + itemHeight + " ; scrollY = " + scrollY)
setTimeout(() => {
if (scrollY < 0) {
this.scroller.scrollEdge(Edge.Start);
} else {
this.scroller.scrollTo({ xOffset: 0, yOffset: scrollY });
}
}, 0);
}
}
}
private getSelectItemPos(fileList: Array<FilesData>, allData: Array<FilesData>): number {
if (ArrayUtil.isEmpty(allData)) {
allData = [];
}
if (!ArrayUtil.isEmpty(fileList)) {
for (let index = 0; index < fileList.length; index++) {
const fileData: FilesData = fileList[index];
allData.push(fileData);
if (fileData.uri === this.selectUri) {
return allData.length;
}
if (fileData.hasSubFolderList()) {
let subFolderList: Array<FilesData> = fileData.getSubFolderList();
let result = this.getSelectItemPos(subFolderList, allData);
if (result > 0) {
return result;
}
}
}
}
return 0;
}
/**
* 加载默认展开目录,如果是路径选择器拉起的,优先使用三方指定的目录
*/
async loadDefaultExpandPath() {
let defaultPickDir = globalThis.keyPathDefaultPickDir;
let loadUri = this.lastSelectPath;
if (!StringUtil.isEmpty(defaultPickDir)) {
loadUri = defaultPickDir;
}
if (!StringUtil.isEmpty(loadUri)) {
let fileHelper = await FileUtil.getFileAccessHelperAsync(globalThis.abilityContext);
let fileInfo = await FileUtil.getFileInfoByUri(loadUri, fileHelper);
if (fileInfo) {
this.defaultExpandPath = FileUtil.getCurrentFolderByFileInfo(fileInfo);
Logger.i(TAG, "loadDefaultExpandPath = " + this.defaultExpandPath);
// 值为true,说明需要刷新树布局,并且传入loadPath
this.isNeedLoadDefaultPath = !StringUtil.isEmpty(this.defaultExpandPath);
}
}
}
private canCreateFolder(): boolean {
if (this.chooseItem && this.chooseItem.layer) {
return this.chooseItem.layer < FOLDER_LEVEL.MAX_LEVEL;
}
return true;
}
build() {
Column() {
Row() {
Image($r("app.media.hidisk_cancel_normal"))
.width($r('app.float.common_size46'))
.height($r('app.float.common_size30'))
.height($r('app.float.common_size46'))
.objectFit(ImageFit.Contain)
.margin({ top: $r('app.float.common_margin10') })
.padding($r('app.float.common_padding10'))
.stateStyles({
pressed: pressedStyles
pressed: pressedStyles,
normal: normalStyles
})
.interpolation(ImageInterpolation.Medium)
.onClick(() => {
this.moveCallback.call(this, {
cancel: true
@@ -131,34 +240,40 @@ export struct fileTree {
Blank()
Image($r('app.media.hidisk_ic_add_folder'))
.width($r('app.float.common_size46'))
.height($r('app.float.common_size30'))
.height($r('app.float.common_size46'))
.objectFit(ImageFit.Contain)
.margin({
left: $r('app.float.common_margin2'),
top: $r('app.float.common_margin10'),
right: $r('app.float.common_margin10')
})
.margin({ left: $r('app.float.common_margin2') })
.padding($r('app.float.common_padding10'))
.stateStyles({
pressed: pressedStyles
pressed: pressedStyles,
normal: normalStyles
})
.enabled(this.canCreateFolder())
.opacity(this.canCreateFolder() ? $r('app.float.common_opacity10') : $r('app.float.common_opacity2'))
.interpolation(ImageInterpolation.Medium)
.onClick(() => {
this.fileMkdirDialog.open()
})
Image($r('app.media.ic_ok'))
.width($r('app.float.common_size46'))
.height($r('app.float.common_size30'))
.height($r('app.float.common_size46'))
.objectFit(ImageFit.Contain)
.margin({ left: $r('app.float.common_margin2'), top: $r('app.float.common_margin10') })
.objectFit(ImageFit.Contain)
.margin({ left: $r('app.float.common_margin2') })
.padding($r('app.float.common_padding10'))
.stateStyles({
pressed: pressedStyles
pressed: pressedStyles,
normal: normalStyles
})
.onClick(() => {
.onClick(async () => {
setPreferencesValue(FILE_MANAGER_PREFERENCES.name, FILE_MANAGER_PREFERENCES.lastSelectPath.key, this.selectUri);
AppStorage.SetOrCreate<string>(FILE_MANAGER_PREFERENCES.lastSelectPath.key, this.selectUri);
const prefix = this.fileName.trim()
if (!prefix) {
this.errorText = $r('app.string.input_nothing')
return
}
const fileName = `${prefix}${this.suffix ? '.' : ''}${this.suffix}`
const fileName = this.fileName.trim()
if (StringUtil.getBytesCount(fileName) > FILENAME_MAX_LENGTH) {
this.errorText = $r('app.string.max_input_length')
} else if (!isValidFileName(fileName)) {
@@ -178,7 +293,6 @@ export struct fileTree {
bottom: $r('app.float.common_padding30'),
left: $r('app.float.common_padding15')
})
.borderRadius($r('app.float.common_borderRadius10'))
Row() {
if (this.listLength > 1) {
@@ -209,6 +323,7 @@ export struct fileTree {
if (this.listLength <= 1) {
Column() {
TextInput({ text: this.fileName })
.fontSize($r('app.float.common_font_size16'))
.backgroundColor($r('app.color.text_input_bg_color'))
.onChange((newVal) => {
this.fileName = newVal
@@ -237,7 +352,7 @@ export struct fileTree {
.margin({ bottom: $r('app.float.common_size10') })
}
Row().width('100%').height($r('app.float.common_size6')).backgroundColor($r('app.color.move_dialog_divider'))
Row().width('100%').height($r('app.float.common_size4')).opacity(0.05).backgroundColor($r('app.color.black'))
Row() {
Image($r('app.media.hidisk_ic_classify_phone'))
@@ -252,9 +367,10 @@ export struct fileTree {
.layoutWeight(1)
Image($r('app.media.ic_arrow_right'))
.objectFit(ImageFit.Contain)
.renderMode(ImageRenderMode.Original)
.aspectRatio(1)
.width($r('app.float.common_size15'))
.autoResize(true)
.height($r('app.float.common_size12'))
.width($r('app.float.common_size12'))
.interpolation(ImageInterpolation.Medium)
.rotate({ z: 90, angle: this.topRotate ? 90 : 0 })
}
.width('100%')
@@ -264,7 +380,7 @@ export struct fileTree {
left: $r('app.float.common_padding24'),
right: $r('app.float.common_padding24')
})
.backgroundColor(this.isSelectRootPath ? $r('app.color.move_dialog_background') : '')
.backgroundColor(this.isSelectRootPath ? $r('app.color.path_pick_selected_bg') : '')
.onClick(async () => {
this.selectName = getResourceString($r('app.string.myPhone'))
this.selectUri = globalThis.documentInfo && globalThis.documentInfo?.uri
@@ -274,34 +390,52 @@ export struct fileTree {
this.fileList = fileList
this.rootData = folderList
this.folderList = this.rootData
this.isClickExpand = true;
this.defaultExpandPath = '';
})
Scroll() {
Scroll(this.scroller) {
Column() {
if (this.rootData.length && this.topRotate) {
ForEach(this.rootData, (item) => {
TreeItem({
fileItem: item,
selectUri: $selectUri,
chooseItem: $chooseItem,
selectName: $selectName,
layer: 2,
folderList: $folderList,
fileList: $fileList
})
if (this.isNeedLoadDefaultPath) {
TreeItem({
fileItem: item,
loadPath: this.defaultExpandPath,
selectUri: $selectUri,
chooseItem: $chooseItem,
selectName: $selectName,
layer: 2,
folderList: $folderList,
fileList: $fileList,
isClickExpand: $isClickExpand
})
} else {
TreeItem({
fileItem: item,
selectUri: $selectUri,
chooseItem: $chooseItem,
selectName: $selectName,
layer: 2,
folderList: $folderList,
fileList: $fileList,
isClickExpand: $isClickExpand
})
}
})
}
}
}
.width('100%')
.scrollBar(BarState.Off)
.padding({ bottom: $r('app.float.common_padding2') })
.layoutWeight(1)
.padding({ bottom: $r('app.float.common_padding10') })
.align(Alignment.TopStart)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.white'))
.borderRadius($r('app.float.common_borderRadius8'))
.borderRadius({ topLeft: $r('app.float.common_size24'), topRight: $r('app.float.common_size24') })
.position({ y: this.positionY })
.onAppear(() => {
@@ -20,6 +20,11 @@ import { BreadData } from '../../../databases/model/FileData'
.borderRadius($r('app.float.common_borderRadius8'))
}
@Styles function normalStyles () {
.backgroundColor($r('app.color.transparent_color'))
.borderRadius($r('app.float.common_borderRadius8'))
}
@Component
export struct BreadCrumb {
@Link @Watch("onDireListUpdated") direList: BreadData[]
@@ -27,7 +32,9 @@ export struct BreadCrumb {
// 监听面包屑变化,滚动到指定位置
onDireListUpdated(): void {
this.scroller.scrollEdge(Edge.End)
setTimeout(() => {
this.scroller.scrollEdge(Edge.End)
}, 10);
}
build() {
@@ -40,13 +47,15 @@ export struct BreadCrumb {
.fontColor(this.direList.length ? $r('app.color.detail_path_text_color') : $r('app.color.black'))
.textOverflow({ overflow: TextOverflow.Ellipsis })
.stateStyles({
pressed: pressedStyles
pressed: pressedStyles,
normal: normalStyles
})
if (this.direList.length) {
Image($r("app.media.ic_arrow_right"))
.objectFit(ImageFit.Contain)
.height($r('app.float.common_size15'))
.width($r('app.float.common_size15'))
.interpolation(ImageInterpolation.Medium)
}
}
.onClick(() => {
@@ -103,7 +112,8 @@ struct BreadCrumbItem {
.textAlign(TextAlign.Center)
.maxLines(1)
.stateStyles({
pressed: pressedStyles
pressed: pressedStyles,
normal: normalStyles
})
if (!this.isLast()) {
Image($r("app.media.ic_arrow_right"))
@@ -30,6 +30,11 @@ const filePickerViewFlag = globalThis.filePickerViewFlag
.backgroundColor($r('app.color.hicloud_hmos_bg'))
}
@Styles function normalStyles() {
.borderRadius($r('app.float.common_borderRadius8'))
.backgroundColor($r('app.color.transparent_color'))
}
@Extend(Text) function grayText() {
.fontSize($r('app.float.common_font_size12'))
.fontColor($r('app.color.black'))
@@ -144,6 +149,9 @@ struct FileListItem {
getRightIcon(item): Resource {
if (this.isMulti) {
if (pickerStatus(this.fileItem, this.checkedNum).differentTypes) {
return
}
return item.isChecked ? $r("app.media.checkbox_b") : $r("app.media.checkbox_g")
} else if (item.isFolder && this.isList) {
return $r("app.media.ic_arrow_right")
@@ -163,7 +171,8 @@ struct FileListItem {
this.onClickEvent()
})
.stateStyles({
pressed: pressedStyles
pressed: pressedStyles,
normal: normalStyles
})
.gesture(
LongPressGesture({ repeat: false })
@@ -181,11 +190,15 @@ struct FileListItem {
Logger.i(TAG, 'longPressEvent start');
if (!this.isMulti) {
this.isMulti = true
if (pickerStatus(this.fileItem, this.checkedNum).exceedLimit) {
if (this.fileItem.isFolder) {
return
}
let status = pickerStatus(this.fileItem, this.checkedNum);
if (status.exceedLimit) {
filePickerTip()
return
}
if (!this.fileItem.isFolder && pickerStatus(this.fileItem, this.checkedNum).differentTypes) {
if (status.differentTypes) {
return
}
@@ -195,22 +208,18 @@ struct FileListItem {
}
}
/**
* 选择器页面,判断文件不是用户期望的类型
*/
notTargetType(): boolean {
return!this.fileItem.isFolder
&& pickerStatus(this.fileItem, this.checkedNum).differentTypes
}
onClickEvent() {
Logger.i(TAG, 'onClickEvent start');
let status = pickerStatus(this.fileItem, this.checkedNum);
if (this.isMulti) {
if (this.fileItem.isFolder) {
return
}
// 选择器页面,选择文件超出上限
if (pickerStatus(this.fileItem, this.checkedNum).exceedLimit) {
if (status.exceedLimit) {
filePickerTip()
return
} else if (this.notTargetType()) {
} else if (status.differentTypes) {
return
}
@@ -225,15 +234,15 @@ struct FileListItem {
}
}
} else {
if (this.notTargetType()) {
return
}
if (!pickerStatus(this.fileItem, this.checkedNum).exceedLimit
&& !pickerStatus(this.fileItem, this.checkedNum).differentTypes
&& !this.fileItem.isFolder) {
// 选中的数据回调给三方应用
this.fileItem.pickFile()
return
if (!this.fileItem.isFolder) {
if (status.differentTypes) {
return
}
if (!status.exceedLimit && !status.differentTypes) {
// 选中的数据回调给三方应用
this.fileItem.pickFile()
return
}
}
this.clickFolder()
}
@@ -251,10 +260,9 @@ struct FileListItem {
}
calOpacity() {
return pickerStatus(this.fileItem, this.checkedNum).exceedLimit
|| (!this.fileItem.isFolder &&
pickerStatus(this.fileItem, this.checkedNum)
.differentTypes) ? $r('app.float.common_opacity2') : $r('app.float.common_opacity10')
const statusObj = pickerStatus(this.fileItem, this.checkedNum)
return statusObj.exceedLimit || (!this.fileItem.isFolder && statusObj.differentTypes) || (this.isMulti && this.fileItem.isFolder)
? $r('app.float.common_opacity2') : $r('app.float.common_opacity10')
}
@Builder
@@ -267,9 +275,9 @@ struct FileListItem {
.height($r('app.float.common_size36'))
.width($r('app.float.common_size36'))
.borderRadius($r('app.float.album_borderRadius_5'))
.onError(() => {
.onError((error) => {
this.isImageLoaded = false
Logger.e(TAG, 'onError: ' + this.fileItem.fileName + ', ' + this.fileItem.thumbUri)
Logger.e(TAG, 'onError: ' + this.fileItem.fileName + ', ' + this.fileItem.thumbUri + ',' + JSON.stringify(error))
})
if (isDlpFile(this.fileItem.fileName)) {
Image($r("app.media.lockNoBorder"))
@@ -311,11 +319,13 @@ struct FileListItem {
.margin({ left: $r('app.float.common_margin10') })
.width('70%')
Image(this.getRightIcon(this.fileItem))
.objectFit(ImageFit.Contain)
.autoResize(false)
.height($r('app.float.common_size20'))
.width($r('app.float.common_size20'))
if ((this.fileItem.isFolder && !this.isMulti) || (this.isMulti && !this.fileItem.isFolder)) {
Image(this.getRightIcon(this.fileItem))
.objectFit(ImageFit.Contain)
.autoResize(false)
.height($r('app.float.common_size20'))
.width($r('app.float.common_size20'))
}
}
.height($r('app.float.common_size64'))
.padding({ right: $r('app.float.common_padding16'), left: $r('app.float.common_padding16') })
@@ -334,13 +344,15 @@ struct FileListItem {
.onError(() => {
this.isImageLoaded = false
})
Image(this.getRightIcon(this.fileItem))
.objectFit(ImageFit.Fill)
.width($r('app.float.common_size20'))
.height($r('app.float.common_size20'))
.opacity(true ? 1 : $r('app.float.common_opacity4'))
.markAnchor({ x: $r('app.float.common_vp26'), y: $r('app.float.common_vp26') })
.position({ x: '100%', y: '100%' })
if ((this.fileItem.isFolder && !this.isMulti) || (this.isMulti && !this.fileItem.isFolder)) {
Image(this.getRightIcon(this.fileItem))
.objectFit(ImageFit.Fill)
.width($r('app.float.common_size20'))
.height($r('app.float.common_size20'))
.opacity(true ? 1 : $r('app.float.common_opacity4'))
.markAnchor({ x: $r('app.float.common_vp26'), y: $r('app.float.common_vp26') })
.position({ x: '100%', y: '100%' })
}
if (isDlpFile(this.fileItem.fileName)) {
Image($r("app.media.lockNoBorder"))
.objectFit(ImageFit.Fill)
+25 -15
View File
@@ -2,7 +2,7 @@
"module": {
"name": "entry",
"type": "entry",
"srcEntrance": "./ets/application/PickerAbilityStage.ets",
"srcEntry": "./ets/application/PickerAbilityStage.ets",
"description": "$string:module_desc",
"mainElement": "MainAbility",
"deviceTypes": [
@@ -12,22 +12,16 @@
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"metadata": [
{
"name": "ArkTSPartialUpdate",
"value": "false"
}
],
"abilities": [
{
"name": "MainAbility",
"srcEntrance": "./ets/entryability/MainAbility.ts",
"srcEntry": "./ets/entryability/MainAbility.ts",
"description": "$string:EntryAbility_desc",
"icon": "$media:app_icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:app_icon",
"startWindowBackground": "$color:start_window_background",
"visible": true,
"exported": true,
"skills": [
{
"actions": [
@@ -41,24 +35,40 @@
requestPermissions: [
// 媒体库管理权限
{
name: "ohos.permission.MEDIA_LOCATION"
name: "ohos.permission.MEDIA_LOCATION",
"reason": "$string:permission_storage_reason_tips",
usedScene: {
"when": "always",
"abilities": ['MainAbility']
}
},
{
name: "ohos.permission.READ_MEDIA"
name: "ohos.permission.READ_MEDIA",
"reason": "$string:permission_storage_reason_tips",
usedScene: {
"when": "always",
"abilities": ['MainAbility']
}
},
{
name: "ohos.permission.WRITE_MEDIA"
name: "ohos.permission.WRITE_MEDIA",
"reason": "$string:permission_storage_reason_tips",
usedScene: {
"when": "always",
"abilities": ['MainAbility']
}
},
// 文件管理权限
{
name: "ohos.permission.FILE_ACCESS_MANAGER"
},
// 应用程序包管理权限
{
name: 'ohos.permission.GET_BUNDLE_INFO'
},
{
name: 'ohos.permission.GET_BUNDLE_INFO_PRIVILEGED'
},
// 公共目录uri代理授权的权限
{
"name": "ohos.permission.PROXY_AUTHORIZATION_URI"
}
]
}
@@ -59,6 +59,14 @@
{
"name": "error_message_color",
"value": "#FF3300"
},
{
"name": "path_pick_selected_bg",
"value": "#1A254FF7"
},
{
"name": "list_item_pressed_bg",
"value": "#1A000000"
}
]
}
@@ -251,6 +251,10 @@
{
"name": "text_input_margin_minus20",
"value": "-20vp"
},
{
"name": "common_size56",
"value": "56vp"
}
]
}
@@ -111,6 +111,10 @@
{
"name": "max_input_length",
"value": "Maximum length reached"
},
{
"name": "permission_storage_reason_tips",
"value": "This is used to acquire and store photos, files, and other media content."
}
]
}
@@ -111,6 +111,14 @@
{
"name": "max_input_length",
"value": "Maximum length reached"
},
{
"name": "save_file_has_same_file",
"value": "Duplicate name file already exists"
},
{
"name": "permission_storage_reason_tips",
"value": "This is used to acquire and store photos, files, and other media content."
}
]
}
@@ -111,6 +111,14 @@
{
"name": "max_input_length",
"value": "已达到长度上限"
},
{
"name": "save_file_has_same_file",
"value": "已有重名文件"
},
{
"name": "permission_storage_reason_tips",
"value": "用于获取和存储照片、媒体内容和文件"
}
]
}
+18
View File
@@ -0,0 +1,18 @@
{
"hvigorVersion": "2.0.0",
"dependencies": {
"@ohos/hvigor-ohos-plugin": "2.0.0"
},
"execution": {
// "daemon": true, /* Enable daemon compilation. Default: true */
// "incremental": true, /* Enable incremental compilation. Default: true */
// "parallel": true, /* Enable parallel compilation. Default: true */
// "typeCheck": false, /* Enable typeCheck. Default: false */
},
"logging": {
// "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
},
"debugging": {
// "stacktrace": false /* Disable stacktrace compilation. Default: false */
}
}
File diff suppressed because one or more lines are too long
+48
View File
@@ -0,0 +1,48 @@
#!/bin/bash
# ----------------------------------------------------------------------------
# Hvigor startup script, version 1.0.0
#
# Required ENV vars:
# ------------------
# NODE_HOME - location of a Node home dir
# or
# Add /usr/local/nodejs/bin to the PATH environment variable
# ----------------------------------------------------------------------------
HVIGOR_APP_HOME="`pwd -P`"
HVIGOR_WRAPPER_SCRIPT=${HVIGOR_APP_HOME}/hvigor/hvigor-wrapper.js
warn() {
echo ""
echo -e "\033[1;33m`date '+[%Y-%m-%d %H:%M:%S]'`$@\033[0m"
}
error() {
echo ""
echo -e "\033[1;31m`date '+[%Y-%m-%d %H:%M:%S]'`$@\033[0m"
}
fail() {
error "$@"
exit 1
}
# Determine node to start hvigor wrapper script
if [ -n "${NODE_HOME}" ];then
EXECUTABLE_NODE="${NODE_HOME}/bin/node"
if [ ! -x "$EXECUTABLE_NODE" ];then
fail "ERROR: NODE_HOME is set to an invalid directory,check $NODE_HOME\n\nPlease set NODE_HOME in your environment to the location where your nodejs installed"
fi
else
EXECUTABLE_NODE="node"
which ${EXECUTABLE_NODE} > /dev/null 2>&1 || fail "ERROR: NODE_HOME is not set and not 'node' command found in your path"
fi
# Check hvigor wrapper script
if [ ! -r "$HVIGOR_WRAPPER_SCRIPT" ];then
fail "ERROR: Couldn't find hvigor/hvigor-wrapper.js in ${HVIGOR_APP_HOME}"
fi
# start hvigor-wrapper script
exec "${EXECUTABLE_NODE}" \
"${HVIGOR_WRAPPER_SCRIPT}" "$@"
+64
View File
@@ -0,0 +1,64 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Hvigor startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
set WRAPPER_MODULE_PATH=%APP_HOME%\hvigor\hvigor-wrapper.js
set NODE_EXE=node.exe
goto start
:start
@rem Find node.exe
if defined NODE_HOME goto findNodeFromNodeHome
%NODE_EXE% --version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: NODE_HOME is not set and no 'node' command could be found in your PATH.
echo.
echo Please set the NODE_HOME variable in your environment to match the
echo location of your NodeJs installation.
goto fail
:findNodeFromNodeHome
set NODE_HOME=%NODE_HOME:"=%
set NODE_EXE_PATH=%NODE_HOME%/%NODE_EXE%
if exist "%NODE_EXE_PATH%" goto execute
echo.
echo ERROR: NODE_HOME is not set and no 'node' command could be found in your PATH.
echo.
echo Please set the NODE_HOME variable in your environment to match the
echo location of your NodeJs installation.
goto fail
:execute
@rem Execute hvigor
"%NODE_EXE%" "%WRAPPER_MODULE_PATH%" %*
if "%ERRORLEVEL%" == "0" goto hvigorwEnd
:fail
exit /b 1
:hvigorwEnd
if "%OS%" == "Windows_NT" endlocal
:end
+11
View File
@@ -0,0 +1,11 @@
{
"license": "ISC",
"devDependencies": {
"@ohos/hypium": "1.0.6"
},
"name": "filepicker",
"description": "example description",
"repository": {},
"version": "1.0.0",
"dependencies": {}
}
-1232
View File
File diff suppressed because it is too large Load Diff
-19
View File
@@ -1,19 +0,0 @@
{
"license": "ISC",
"devDependencies": {},
"name": "filepicker",
"ohos": {
"org": "huawei",
"directoryLevel": "project",
"buildTool": "hvigor"
},
"description": "example description",
"repository": {},
"version": "1.0.0",
"dependencies": {
"@ohos/hypium": "1.0.1",
"@ohos/hvigor-ohos-plugin": "1.1.6",
"hypium": "^1.0.0",
"@ohos/hvigor": "1.1.6"
}
}