!82 filePicker适配半模态

Merge pull request !82 from 田岚清/master
This commit is contained in:
openharmony_ci
2024-06-28 09:34:48 +00:00
committed by Gitee
22 changed files with 1674 additions and 323 deletions
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2022 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 enum PreferenceMode {
FOLDER = 'FolderPreference',
FILE_PICKER = 'FilePickerFolderPreference'
}
export enum UnityStartMode {
NORMAL,
FILE_PICKER_OPEN_FILE,
FILE_PICKER_OPEN_FOLDER,
FILE_PICKER_OPEN_MIXED,
FILE_PICKER_CREATE,
DESKTOP_FILE_START,
GRANT_PERMISSION
}
export enum SelectMode {
FILE,
FOLDER,
MIXED
}
export enum PickerWindowType {
ABILITY = 'Ability',
SERVICE = 'ServiceExtensionAbility',
UI = 'UIExtensionAbility'
}
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2022 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.
*/
/**
* uri of the virtual directory
*/
export enum VirtualUri {
// Quick Access
RECENT = 'recent',
DESKTOP = 'file://docs/storage/Users/currentUser/Desktop',
DOWNLOAD = 'file://docs/storage/Users/currentUser/Download',
RECYCLE_BIN = 'recycleBin',
// Storage Location
MY_PC = 'file://docs/storage/Users/currentUser',
DOCUMENT = 'file://docs/storage/Users/currentUser/Documents',
EXTERNAL_DISK = 'file://docs/storage/External',
PCENGINE = 'file://docs/storage/Users/currentUser/PCEngine',
// gallery
GALLERY = 'gallery',
// fileTag
FILE_TAG = 'fileTag',
}
@@ -0,0 +1,196 @@
/*
* Copyright (c) 2023-2024 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 { common, Context, UIExtensionContentSession } from '@kit.AbilityKit';
import { PickerWindowType, SelectMode, UnityStartMode } from '../constants/FilePickerItems';
export class StartModeOptions {
/**************************** picker *********************************/
/************** common *******************/
/**
* picker场景下,界面模式
*/
public windowType = PickerWindowType.ABILITY;
public session: UIExtensionContentSession;
/**
* want对应的action
*/
public action: string = '';
/**
* 拉起picker的应用ability名
*/
public callerAbilityName: string = '';
/**
* 拉起picker的包名
*/
public callerBundleName: string = '';
/**
* 选择器callerId
*/
public callerUid: number = 0;
/**
* 拉起picker的默认文件或者指定目录
*/
public defaultFilePathUri: string = '';
/**
* 代表拉起sysPicker/filePicker类型的ExtensionAbility
*/
public extType: string = '';
/**
* UIExtensionContext
*/
public uiExtContext: common.UIExtensionContext;
/**
* UIExtensionContext
*/
public uiContext: common.UIAbilityContext;
/**
* context
*/
public context: Context;
/**
* 用来区分选择,保存还是下载模式
* 当pickerType设置为downloadAuth时,用户配置的参数newFileNames、defaultFilePathUri和fileSuffixChoices将不会生效
*/
public pickerType: string = 'DEFAULT';
/************** select *******************/
/**
* 选择文件的后缀类型
*/
public fileSuffixFilters: string[] = [];
/**
* 选择文件最大个数,上限500, 默认为1
*/
public maxSelectNumber: number = 1;
/**
* 支持选择的资源类型,比如:文件、文件夹和二者混合
*/
public selectMode: SelectMode = SelectMode.FILE;
/**
* 当为授权模式,defaultFilePathUri必填,表明待授权uri
*/
public isAuthMode: boolean = false;
/**
* 调用方传入文件类型(兼容双框架action)
*/
public phonePickerType: string = '';
/**
* 调用方传入文件类型列表
*/
public phonePickerTypeList: string[] = [];
/************** save *******************/
/**
* 进行保存的文件名列表
*/
public newFileNames: string[] = [];
/**
* 保存文件的后缀类型
*/
public fileSuffixChoices: string[] = [];
/**
* 手机保存文件的后缀类型
*/
public PhoneFileSuffixChoices: string = '';
/**************************** 主界面模式 *********************************/
/**
* 文管启动模式,只允许初始化过程修改一次,默认为主界面模式
*/
public startMode: UnityStartMode = UnityStartMode.NORMAL;
public getFileSuffixFilterList(): string[] {
if (this.fileSuffixFilters.length === 0) {
this.fileSuffixFilters.push('.*');
}
return this.fileSuffixFilters;
}
public setSelectMode(mode: number | undefined): void {
if (mode === undefined) {
this.selectMode = SelectMode.FILE;
return;
}
if ((mode < SelectMode.FILE) || (mode > SelectMode.MIXED)) {
this.selectMode = SelectMode.FILE;
return;
}
this.selectMode = mode;
}
public setNewFileNames(names: string[] | undefined): void {
if (names === undefined) {
this.newFileNames = [];
return;
}
if (names.length === 0) {
this.newFileNames = [''];
return;
}
this.newFileNames = names;
}
public isDownloadMode(): boolean {
return this.pickerType === 'downloadAuth';
}
public isGrantPermissionMode(): boolean {
return this.isAuthMode;
}
public isSelectFolderMode(): boolean {
return this.startMode === UnityStartMode.FILE_PICKER_OPEN_FOLDER;
}
public isOpenFileMode(): boolean {
// 新方案需要通过pickerType区分
return this.action === 'ohos.want.action.OPEN_FILE_SERVICE' || this.action === 'ohos.want.action.OPEN_FILE';
}
public isCreateFileMode(): boolean {
// 新方案需要通过pickerType区分
return this.action === 'ohos.want.action.CREATE_FILE_SERVICE' || this.action === 'ohos.want.action.CREATE_FILE';
}
public isUxt(): boolean {
return this.windowType === PickerWindowType.UI;
}
public setUiExtContext(context: common.UIExtensionContext): void {
this.uiExtContext = context;
}
}
@@ -25,19 +25,37 @@ import { FILE_MANAGER_PREFERENCES, FILE_SUFFIX, SELECT_MODE } from '../constants
import StringUtil from './StringUtil'
import { ArrayUtil } from './ArrayUtil'
import { getPreferences } from './PreferencesUtil'
import { ability, Want } from '@kit.AbilityKit'
import { StartModeOptions } from '../model/StartModeOptions'
import ctx from '@ohos.app.ability.common';
import { PickerWindowType } from '../constants/FilePickerItems'
const TAG = 'AbilityCommonUtil'
const BUNDLE_NAME = 'com.ohos.filepicker'
let mediaLibrary: MediaLibrary.MediaLibrary = null
/**
* picker对外返回的响应码
*/
export enum ResultCodePicker {
SUCCESS = 0,
CANCEL = -1
}
interface abilityResultInterface {
want: Want,
resultCode: number
};
/**
* Ability公共工具类
*/
namespace AbilityCommonUtil {
/**
* 需要用户授权的权限列表
*/
/**
* 需要用户授权的权限列表
*/
export const PERMISSION_LIST: Array<Permissions> = [
"ohos.permission.MEDIA_LOCATION",
"ohos.permission.READ_MEDIA",
@@ -79,6 +97,7 @@ namespace AbilityCommonUtil {
FILE_PICKER: 'FilePickerAbility',
PATH_PICKER: 'PathPickerAbility'
}
/**
* 拉起Ability时必要的初始化操作
*/
@@ -108,7 +127,8 @@ 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+','+rootInfo.relativePath)
Logger.i(TAG, 'RootInfo: ' + rootInfo.uri + ', ' + rootInfo.deviceType + ', ' + rootInfo.deviceFlags + ', ' +
rootInfo.displayName + ',' + rootInfo.relativePath)
rootInfoArr.push(rootInfo)
result = rootIterator.next()
isDone = result.done
@@ -185,14 +205,14 @@ namespace AbilityCommonUtil {
grantSuccessCount++;
} catch (error) {
resolve(false);
Logger.e(TAG, `grantUriPermission fail,grantSuccessCount:${grantSuccessCount}}, 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)
})
}
/**
@@ -201,78 +221,64 @@ namespace AbilityCommonUtil {
* @param result
* @param message
*/
export async function terminateFilePicker(result: Array<string> = [], displayNames: Array<string> = [], resultCode: number = RESULT_CODE.SUCCESS, message: string = ''): Promise<void> {
const bundleName = globalThis.pickerCallerBundleName
if (result.length && bundleName) {
// uri授权
const isSuccess = await grantUriPermission(result, bundleName);
if (!isSuccess) {
resultCode = ErrorCodeConst.PICKER.GRANT_URI_PERMISSION_FAIL,
result = []
message = 'uri grant permission fail'
displayNames = []
export async function terminateFilePicker(result: string[] = [],
resultCode: number = ResultCodePicker.SUCCESS, startModeOptions: StartModeOptions): Promise<void> {
Logger.i(TAG, 'enter terminateFilePicker, result length ' + result.length + ', resultCode:' + resultCode);
let want: Want = {
bundleName: BUNDLE_NAME,
flags: wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION,
parameters: {
'ability.params.stream': result
}
}
let abilityResult = {
resultCode: resultCode,
want: {
bundleName: globalThis.abilityContext.abilityInfo.bundleName,
abilityName: ABILITY_LIST.FILE_PICKER,
parameters: {
'select_item_list': result,
'file_name_list': displayNames,
message: message,
'result': result[0],
}
}
}
globalThis.abilityContext.terminateSelfWithResult(abilityResult, (error) => {
if (error.code) {
Logger.e(TAG, 'terminateFilePicker failed. Cause: ' + JSON.stringify(error))
return
}
Logger.d(TAG, 'terminateFilePicker success. result: ' + JSON.stringify(abilityResult))
})
};
returnAbilityResult(want, resultCode, startModeOptions);
}
/**
* 文件创建完成,返回uri列表
* @param result
* @param resultCode
* @param message
*/
export async function terminatePathPicker(result: Array<string>, resultCode: number = RESULT_CODE.SUCCESS, message: string = ''): Promise<void> {
const bundleName = globalThis.pathCallerBundleName
if (result.length && bundleName) {
// uri授权
const isSuccess = await grantUriPermission(result, bundleName);
if (!isSuccess) {
resultCode = ErrorCodeConst.PICKER.GRANT_URI_PERMISSION_FAIL,
result = []
message = 'uri grant permission fail'
export async function terminatePathPicker(result: string[],
resultCode: number = ResultCodePicker.SUCCESS, startModeOptions: StartModeOptions): Promise<void> {
Logger.i(TAG, 'enter terminatePathPicker, result length ' + result.length + ', resultCode:' + resultCode);
let want: Want = {
bundleName: BUNDLE_NAME,
abilityName: ABILITY_LIST.PATH_PICKER,
flags: wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION,
parameters: {
'ability.params.stream': result,
KEY_PICK_SELECT_CLOUD_DISK: false
}
};
returnAbilityResult(want, resultCode, startModeOptions);
}
export function returnAbilityResult(want: Want, resultCode: number, options: StartModeOptions) {
Logger.i(TAG, 'returnPicker start');
if (options.windowType === PickerWindowType.ABILITY) {
let abilityResult: abilityResultInterface = {
want: want,
resultCode: resultCode
};
Logger.i(TAG, 'uiContext terminateSelfWithResult start');
options.uiContext.terminateSelfWithResult(abilityResult, (error) => {
Logger.i(TAG, 'terminateSelfWithResult is called = ' + error.code);
});
} else {
let abilityResult: ability.AbilityResult = {
resultCode: resultCode,
want: want
};
Logger.i(TAG, 'session terminateSelfWithResult start');
options.session.terminateSelfWithResult(abilityResult, (error) => {
Logger.e(TAG, 'closeUIExtFilePicker terminateSelfWithResult is called = ' + error?.code);
});
}
let abilityResult = {
resultCode: resultCode,
want: {
bundleName: globalThis.pathAbilityContext.abilityInfo.bundleName,
abilityName: ABILITY_LIST.PATH_PICKER,
parameters: {
'pick_path_return': result,
'key_pick_select_clouddisk': false,
'message': message,
// 兼容老版本picker
'result': result[0]
}
}
}
globalThis.pathAbilityContext.terminateSelfWithResult(abilityResult, (error) => {
if (error.code) {
Logger.e(TAG, 'terminatePathPicker failed. Cause: ' + JSON.stringify(error))
return
}
Logger.d(TAG, 'terminatePathPicker success. result: ' + JSON.stringify(abilityResult))
})
}
/**
@@ -293,8 +299,8 @@ namespace AbilityCommonUtil {
* @param keyPickType 调用方传入文件类型(兼容双框架action)
* @param keyPickTypeList 调用方传入文件类型列表
*/
export function getKeyPickTypeList(keyPickType, keyPickTypeList): Array<string>{
let typeList =[]
export function getKeyPickTypeList(keyPickType, keyPickTypeList): Array<string> {
let typeList = []
if (keyPickType) {
typeList.push(keyPickType)
}
@@ -17,6 +17,86 @@ import { toast } from '../../base/utils/Common';
import { FileMimeTypeUtil } from '../../base/utils/FileMimeTypeUtil';
import { FILE_SUFFIX, SELECT_MODE } from '../constants/Constant';
import ObjectUtil from './ObjectUtil';
import { ability, Want } from '@kit.AbilityKit';
import Logger from '../log/Logger';
import { PickerWindowType } from '../constants/FilePickerItems';
import { StartModeOptions } from '../model/StartModeOptions';
import AbilityCommonUtil from './AbilityCommonUtil';
import ctx from '@ohos.app.ability.common';
interface abilityResultInterface {
want: Want,
resultCode: number
};
const TAG = 'FilePickerUtil';
export namespace FilePickerUtil {
export function returnAbilityResult(want: Want, resultCode: number, options: StartModeOptions) {
Logger.i(TAG, 'returnPicker start');
let context = getContext() as ctx.UIAbilityContext;
if (options.windowType === PickerWindowType.ABILITY) {
let abilityResult: abilityResultInterface = {
want: want,
resultCode: resultCode
};
Logger.i(TAG, 'terminateSelfWithResult start');
context.terminateSelfWithResult(abilityResult, (error) => {
Logger.e(TAG, 'terminateSelfWithResult is called = ' + error.code);
});
} else {
let abilityResult: ability.AbilityResult = {
resultCode: resultCode,
want: want
};
options.session?.terminateSelfWithResult(abilityResult, (error) => {
Logger.e(TAG, 'closeUIExtFilePicker terminateSelfWithResult is called = ' + error?.code);
});
}
}
export function getStartModeOptions(want: Want): StartModeOptions {
let options = new StartModeOptions();
if (!want) {
Logger.e(TAG, 'getDocumentSelectOptions want is undefined')
return options;
}
options.action = want.action as string || '';
options.callerAbilityName = want.parameters?.['ohos.aafwk.param.callerAbilityName'] as string || '';
options.callerBundleName = want.parameters?.['ohos.aafwk.param.callerBundleName'] as string || '';
options.callerUid = want?.parameters?.[AbilityCommonUtil.CALLER_UID] as number || 0;
options.defaultFilePathUri = want.parameters?.key_pick_dir_path as string || '';
options.extType = want.parameters?.extType as string || '';
options.pickerType = want.parameters?.pickerType as string || '';
if (options.isOpenFileMode()) {
options.fileSuffixFilters = want.parameters?.key_file_suffix_filter as string[] || [];
options.maxSelectNumber = want.parameters?.key_pick_num as number || 1;
options.setSelectMode(want.parameters?.key_select_mode as number);
options.isAuthMode = want.parameters?.key_auth_mode as boolean || false;
} else if (options.isCreateFileMode()) {
options.setNewFileNames(want.parameters?.key_pick_file_name as string[]);
options.fileSuffixChoices = want.parameters?.key_file_suffix_choices as string[] || [];
} else {
Logger.e(TAG, 'getDocumentSelectOptions mode is error')
}
Logger.i(TAG, 'getDocumentOptions : ' + JSON.stringify(options));
return options;
}
export function getStartOptionsFromStorage(): StartModeOptions {
let storage: LocalStorage = LocalStorage.getShared();
if (!storage) {
Logger.i(TAG, `Storage is null`)
return new StartModeOptions();
}
let options: StartModeOptions | undefined = storage.get<StartModeOptions>('startModeOptions');
if (options === undefined) {
options = new StartModeOptions();
storage.setOrCreate('startModeOptions', options);
}
return options;
}
}
/**
* 文件选择器文件状态
@@ -25,12 +105,12 @@ import ObjectUtil from './ObjectUtil';
* @param checkedNum 选中数量
* @return 是否超限 选择类型是否不匹配
*/
export function pickerStatus(item, checkedNum) {
export function pickerStatus(item, checkedNum, startModeOptions:StartModeOptions) {
return {
// 选择是否超限
exceedLimit: globalThis.filePickerViewFlag && (checkedNum >= globalThis.filePickNum && !item.isChecked),
exceedLimit: checkedNum >= globalThis.filePickNum && !item.isChecked,
// 选择类型是否不匹配
differentTypes: !checkFileSelectable(item)
differentTypes: !checkFileSelectable(item, startModeOptions)
};
}
@@ -38,14 +118,9 @@ export function pickerStatus(item, checkedNum) {
* 根据文件后缀判断文件是否可选
* @param item
*/
function checkFileSelectable(item): boolean {
// 非文件选择器场景
if (!globalThis.filePickerViewFlag) {
return true;
}
function checkFileSelectable(item, startModeOptions: StartModeOptions): boolean {
// selectMode检查
let selectMode: number = globalThis.keySelectMode;
let selectMode: number = startModeOptions.selectMode;
let isFolder = false;
if (ObjectUtil.hasKey(item, 'isFolder')) {
isFolder = item.isFolder;
@@ -64,13 +139,13 @@ function checkFileSelectable(item): boolean {
return false;
}
// 后缀检查
let keyFileSuffixFilter: string[] = globalThis.keyFileSuffixFilter;
let keyFileSuffixFilter: string[] = startModeOptions.fileSuffixFilters;
if (Array.isArray(keyFileSuffixFilter) && keyFileSuffixFilter.length > 0) {
return checkFileSuffix(item.fileName, keyFileSuffixFilter);
}
// mimeType检查
return checkFileMimetype(item.fileName);
return checkFileMimetype(item.fileName, startModeOptions);
}
/**
@@ -99,16 +174,16 @@ function checkFileSuffix(fileName: string, keyFileSuffixFilter: Array<string>):
* @param fileName 文件名称
* @return 条件满足返回true
*/
function checkFileMimetype(fileName: string): boolean {
function checkFileMimetype(fileName: string, startModeOptions: StartModeOptions): boolean {
if (!fileName) {
return false;
}
let keyPickTypeList: string[] = globalThis.keyPickTypeList;
let keyPickTypeList: string[] = startModeOptions.phonePickerTypeList;
// 输入的类型全转换成小写,避免大小敏感问题
keyPickTypeList.forEach(item => item.toLowerCase());
// 类型列表为空或包含*或*/*时,可选择所有文件
if (!keyPickTypeList || keyPickTypeList.length === 0 ||
keyPickTypeList.includes('*') || keyPickTypeList.includes('*/*')) {
keyPickTypeList.includes('*') || keyPickTypeList.includes('*/*')) {
return true;
}
@@ -143,8 +218,9 @@ function checkFileMimetype(fileName: string): boolean {
*
* @param isImmersion 是否沉浸式
*/
export const filePickerTip = () => {
globalThis.abilityContext.resourceManager.getPluralString($r('app.plural.filePickerTip').id, globalThis.filePickNum)
export const filePickerTip = (startModeOptions: StartModeOptions) => {
globalThis.abilityContext.resourceManager.getPluralString($r('app.plural.filePickerTip').id,
startModeOptions.maxSelectNumber)
.then((value) => {
toast(value)
})
+91 -15
View File
@@ -18,14 +18,14 @@ 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 { FILENAME_MAX_LENGTH, RENAME_CONNECT_CHARACTER } from '../constants/Constant';
import MediaLibrary from '@ohos.multimedia.mediaLibrary';
import fs from '@ohos.file.fs';
import FileUri from '@ohos.file.fileuri';
const TAG = 'FileUtil';
export class FileUtil {
/**
* uri 格式开头
*/
@@ -68,7 +68,8 @@ export class FileUtil {
* @param fileAccessHelper fileAccess.FileAccessHelper
* @returns fileAccess.FileInfo
*/
public static async getFileInfoByUri(uri: string, fileAccessHelper: fileAccess.FileAccessHelper): Promise<fileAccess.FileInfo> {
public static async getFileInfoByUri(uri: string,
fileAccessHelper: fileAccess.FileAccessHelper): Promise<fileAccess.FileInfo> {
try {
return await fileAccessHelper.getFileInfoFromUri(uri);
} catch (err) {
@@ -83,7 +84,8 @@ export class FileUtil {
* @param fileAccessHelper fileAccess.FileAccessHelper
* @returns fileAccess.FileInfo
*/
public static async getFileInfoByRelativePath(relativePath: string, fileAccessHelper: fileAccess.FileAccessHelper): Promise<fileAccess.FileInfo> {
public static async getFileInfoByRelativePath(relativePath: string,
fileAccessHelper: fileAccess.FileAccessHelper): Promise<fileAccess.FileInfo> {
try {
return await fileAccessHelper.getFileInfoFromRelativePath(relativePath);
} catch (err) {
@@ -98,7 +100,8 @@ export class FileUtil {
* @param fileAccessHelper
* @returns FileIterator
*/
public static async getFileIteratorByUri(uri: string, fileAccessHelper: fileAccess.FileAccessHelper): Promise<fileAccess.FileIterator> {
public static async getFileIteratorByUri(uri: string,
fileAccessHelper: fileAccess.FileAccessHelper): Promise<fileAccess.FileIterator> {
try {
let fileInfo = await fileAccessHelper.getFileInfoFromUri(uri);
return fileInfo.listFile();
@@ -167,7 +170,8 @@ export class FileUtil {
* @param fileName 文件名
* return 结果
*/
public static async getFileFromFolder(foldrUri: string, fileName, fileAccessHelper: fileAccess.FileAccessHelper): Promise<fileAccess.FileInfo> {
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)) {
@@ -193,7 +197,11 @@ export class FileUtil {
return "";
}
public static async createFolder(fileAccessHelper: fileAccess.FileAccessHelper, parentUri: string, name: string): Promise<{code, uri}> {
public static async createFolder(fileAccessHelper: fileAccess.FileAccessHelper, parentUri: string,
name: string): Promise<{
code,
uri
}> {
let uri: string = '';
let code: any;
try {
@@ -202,7 +210,7 @@ export class FileUtil {
code = error.code;
Logger.e(TAG, 'createFolder error occurred:' + error.code + ', ' + error.message);
}
return {code: code, uri: uri};
return { code: code, uri: uri };
}
public static async hardDelete(uri: string, mediaLibrary: MediaLibrary.MediaLibrary): Promise<boolean> {
@@ -225,19 +233,26 @@ export class FileUtil {
* @param newName newName
* @returns {err, uri}
*/
public static async rename(fileAccessHelper: fileAccess.FileAccessHelper, oldUri: string, newName: string): Promise<{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};
err = { code: error.code, message: error.message };
Logger.e(TAG, 'rename error occurred:' + error.code + ', ' + error.message);
}
return {err: err, uri: uri};
return { err: err, uri: uri };
}
public static async createFile(fileAccessHelper: fileAccess.FileAccessHelper, parentUri: string, fileName: string): Promise<{err, uri}> {
public static async createFile(fileAccessHelper: fileAccess.FileAccessHelper, parentUri: string,
fileName: string): Promise<{
err,
uri
}> {
let retUri: string = '';
let err: any;
try {
@@ -245,9 +260,9 @@ export class FileUtil {
retUri = await fileAccessHelper.createFile(parentUri, fileName);
} catch (e) {
Logger.e(TAG, 'createFile error: ' + e.code + ', ' + e.message);
err = {code: e.code, message: e.message};
err = { code: e.code, message: e.message };
}
return {err: err, uri: retUri};
return { err: err, uri: retUri };
}
public static hasSubFolder(loadPath: string, curFolderPath: string): boolean {
@@ -338,4 +353,65 @@ export class FileUtil {
}
return null;
}
/**
* 根据文件的沙箱路径获取文件uri
* @param path 文件的沙箱路径
* @returns 文件的uri
*/
public static getUriFromPath(path: string): string {
let uri = '';
try {
// 该接口如果以’/'结尾,返回的uri会以‘/'结尾
uri = FileUri.getUriFromPath(path);
} catch (error) {
Logger.e(TAG, 'getUriFromPath fail, error:' + JSON.stringify(error));
}
return uri;
}
/**
* 将文件uri转换成FileUri对象
*/
public static getFileUriObjectFromUri(uri: string): FileUri.FileUri | undefined {
let fileUriObject: FileUri.FileUri | undefined;
try {
fileUriObject = new FileUri.FileUri(uri);
} catch (error) {
Logger.e(TAG, 'getFileUriObjectFromUri fail, error:' + JSON.stringify(error));
}
return fileUriObject;
}
/**
* 通过将文件uri转换成FileUri对象获取文件的沙箱路径
* @param uri 文件uri
* @returns 文件的沙箱路径
*/
public static getPathFromUri(uri: string): string {
let path = '';
const fileUriObj = FileUtil.getFileUriObjectFromUri(uri);
if (!!fileUriObj) {
path = fileUriObj.path;
}
return path;
}
/**
* 创建文件夹
* @param parentFolderUri 父目录uri
* @param newFolderName 新文件夹名
* @returns 新文件夹uri
*/
public static createFolderByFs(parentFolderUri: string, newFolderName: string): string {
try {
const parentFolderPath = FileUtil.getPathFromUri(parentFolderUri);
const newFolderPath = parentFolderPath + '/' + newFolderName;
fs.mkdirSync(newFolderPath);
return FileUtil.getUriFromPath(newFolderPath);
} catch (error) {
Logger.e(TAG, 'createFolderByFs fail, error:' + JSON.stringify(error));
throw error as Error;
}
}
}
+454
View File
@@ -0,0 +1,454 @@
/*
* Copyright (c) 2023-2024 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 type { BusinessError } from '@ohos.base';
import fs from '@ohos.file.fs';
import fileuri from '@ohos.file.fileuri';
import type uri from '@ohos.uri';
import Logger from '../log/Logger';
export class FsUtil {
static readonly TAG: string = 'FsUtil';
public static async stat(file: string | number): Promise<fs.Stat | BusinessError> {
try {
return await fs.stat(file);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs stat error = ' + JSON.stringify(error));
return error;
}
}
public static statSync(file: string | number): fs.Stat | BusinessError {
try {
return fs.statSync(file);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs statSync error = ' + JSON.stringify(error));
return error;
}
}
public static async access(path: string): Promise<boolean | BusinessError> {
try {
return await fs.access(path);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs access error = ' + JSON.stringify(error));
return error;
}
}
public static accessSync(path: string): boolean {
try {
return fs.accessSync(path);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs accessSync error = ' + JSON.stringify(error));
return false;
}
}
public static openSync(path: string, mode?: number): fs.File | BusinessError {
try {
return fs.openSync(path, mode);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs openSync error = ' + JSON.stringify(error));
return error;
}
}
public static async close(file: number | fs.File): Promise<void | BusinessError> {
try {
return await fs.close(file);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs close error = ' + JSON.stringify(error));
return error;
}
}
public static closeSync(file: number | fs.File): void | BusinessError {
try {
return fs.closeSync(file);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs closeSync error = ' + JSON.stringify(error));
return error;
}
}
public static async mkdir(path: string, recursion: boolean = false): Promise<void | BusinessError> {
try {
return await fs.mkdir(path, recursion);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs mkdir error = ' + JSON.stringify(error));
return error;
}
}
public static mkdirSync(path: string, recursion: boolean = false): void | BusinessError {
try {
return fs.mkdirSync(path, recursion);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs mkdirSync error = ' + JSON.stringify(error));
return error;
}
}
public static async rmdir(path: string): Promise<void | BusinessError> {
try {
return await fs.rmdir(path);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs rmdir error = ' + JSON.stringify(error));
return error;
}
}
public static rmdirSync(path: string): void | BusinessError {
try {
return fs.rmdirSync(path);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs rmdirSync error = ' + JSON.stringify(error));
return error;
}
}
public static async moveFile(src: string, dest: string, mode?: number): Promise<void | BusinessError> {
try {
return await fs.moveFile(src, dest, mode);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs moveFile error = ' + JSON.stringify(error));
return error;
}
}
public static async moveDir(src: string, dest: string, mode?: number): Promise<void | BusinessError> {
try {
return await fs.moveDir(src, dest, mode);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs moveDir error = ' + JSON.stringify(error));
return error;
}
}
public static moveFileSync(src: string, dest: string, mode?: number): void | BusinessError {
try {
return fs.moveFileSync(src, dest, mode);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs moveFileSync error = ' + JSON.stringify(error));
return error;
}
}
public static moveDirSync(src: string, dest: string, mode?: number): void | BusinessError {
try {
return fs.moveDirSync(src, dest, mode);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs moveDirSync error = ' + JSON.stringify(error));
return error;
}
}
public static async rename(oldPath: string, newPath: string): Promise<void | BusinessError> {
try {
return await fs.rename(oldPath, newPath);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs rename error = ' + JSON.stringify(error));
return error;
}
}
public static renameSync(oldPath: string, newPath: string): void | BusinessError {
try {
return fs.renameSync(oldPath, newPath);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs renameSync error = ' + JSON.stringify(error));
return error;
}
}
public static async unlink(path: string): Promise<void | BusinessError> {
try {
return await fs.unlink(path);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs unlink error = ' + JSON.stringify(error));
return error;
}
}
public static unlinkSync(path: string): void | BusinessError {
try {
return fs.unlinkSync(path);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs unlinkSync error = ' + JSON.stringify(error));
return error;
}
}
// @ts-ignore
public static async write(fd: number, buffer: ArrayBuffer | string, options?: fs.WriteOptions): Promise<number | BusinessError> {
try {
return await fs.write(fd, buffer, options);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs write error = ' + JSON.stringify(error));
return error;
}
}
// @ts-ignore
public static writeSync(fd: number, buffer: ArrayBuffer | string, options?: fs.WriteOptions): number | BusinessError {
try {
return fs.writeSync(fd, buffer, options);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs writeSync error = ' + JSON.stringify(error));
return error;
}
}
// @ts-ignore
public static async read(fd: number, buffer: ArrayBuffer, options?: fs.ReadOptions): Promise<number | BusinessError> {
try {
return await fs.read(fd, buffer, options);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs read error = ' + JSON.stringify(error));
return error;
}
}
// @ts-ignore
public static readSync(fd: number, buffer: ArrayBuffer, options?: fs.ReadOptions): number | BusinessError {
try {
return fs.readSync(fd, buffer, options);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs readSync error = ' + JSON.stringify(error));
return error;
}
}
public static readTextSync(path: string): string | BusinessError {
try {
return fs.readTextSync(path);
} catch (error) {
Logger.i(FsUtil.TAG, `fs readTextSync error = ${JSON.stringify(error)}`);
return error;
}
}
// @ts-ignore
public static listFileSync(path: string, options?: fs.ListFileOptions): string[] | BusinessError {
try {
let res = fs.listFileSync(path, options);
return res;
} catch (error) {
Logger.i(FsUtil.TAG, 'fs listFileSync error = ' + JSON.stringify(error));
return error;
}
}
public static async fsync(fd: number): Promise<void | BusinessError> {
try {
return await fs.fsync(fd);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs fsync error = ' + JSON.stringify(error));
return error;
}
}
public static fsyncSync(fd: number): void | BusinessError {
try {
return fs.fsyncSync(fd);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs fsync error = ' + JSON.stringify(error));
return error;
}
}
/**
* 强制删除文件
* @param uri 删除文件的uri
*/
public static forceDelete(uri: string): number | BusinessError {
try {
let fileUri: fileuri.FileUri = new fileuri.FileUri(uri);
let filePath: string = fileUri.path;
if (fs.statSync(filePath).isDirectory()) {
fs.rmdirSync(filePath);
} else {
fs.unlinkSync(filePath);
}
return 0;
} catch (error) {
Logger.i(FsUtil.TAG, 'force delete file error: ' + JSON.stringify(error));
return error;
}
}
/**
* 文件夹判空
* @param path 文件夹的uri
*/
public static isFolderEmpty(path: string): boolean | BusinessError {
try {
let fileList = fs.listFileSync(path, { listNum: 1 });
return fileList.length === 0;
} catch (error) {
Logger.i(FsUtil.TAG, 'isFolderEmpty error: ' + JSON.stringify(error));
return error;
}
}
/**
* 判断文件是否为文件夹(目前暂不支持应用沙箱目录)
* @param path 文件
* @returns
*/
public static isFolder(path: string): boolean | BusinessError {
try {
let stat = fs.statSync(path);
return stat?.isDirectory();
} catch (error) {
Logger.i(FsUtil.TAG, path + ' isFolder error: ' + JSON.stringify(error));
return error;
}
}
/**
* 判断文件是否存在
* @param uri 文件uri
* @returns 判断结果
*/
public static isFileExist(uri: string): boolean {
let isExist: boolean = false;
try {
Logger.i(FsUtil.TAG, 'open start');
let fileFd: fs.File = fs.openSync(uri, fs.OpenMode.READ_ONLY);
fs.closeSync(fileFd);
isExist = true;
} catch (error) {
Logger.i(FsUtil.TAG, 'openSync fail: ' + JSON.stringify(error));
}
return isExist;
}
/**
* 判断文件是否被删除(包括软删除和硬删除)
* @param uri 文件uri
* @returns 判断结果
*/
public static isFileDeleted(uri: string): boolean {
try {
Logger.i(FsUtil.TAG, 'open start');
let fileFd: fs.File = fs.openSync(uri, fs.OpenMode.READ_ONLY); // 此处报错说明被硬删除了
const path = fileFd.path;
fs.closeSync(fileFd);
let stat = fs.statSync(path);
if (stat.ctime === 0 && stat.mtime === 0) { // 说明被软删除了
return true;
}
return false;
} catch (error) {
Logger.i(FsUtil.TAG, 'openSync fail: ' + JSON.stringify(error));
return true;
}
}
/**
* 判断目录下是否存在同名文件
* @param destUri:目录uri
* @param fileName:待判断的文件名
* @returns 判断结果
*/
public static isExistDupName(destUri: string, fileName: string): boolean {
let isExistDupName: boolean = false;
try {
let destFileInfo: uri.URI = new fileuri.FileUri(destUri);
let newFilePath: string = destFileInfo.path + '/' + fileName;
isExistDupName = fs.accessSync(newFilePath);
} catch (err) {
Logger.i(FsUtil.TAG, 'isExistDupName err: ' + JSON.stringify(err));
}
return isExistDupName;
}
public static getInoByUri(uri: string): string {
try {
let fileUri: fileuri.FileUri = new fileuri.FileUri(uri);
let stat = fs.statSync(fileUri.path);
return stat.ino.toString();
} catch (error) {
Logger.i(FsUtil.TAG, `get ino failed, error message : ${error?.message}, error code : ${error?.code}`);
return '';
}
}
/**
* 文件拷贝同步接口,适合十几兆的小文件拷贝
* @param srcPath 源文件
* @param destinationPath 目标文件
* @param mode 拷贝模式
* @returns true:拷贝成功,目标文件已经存在
*/
public static copyFileSyncByPath(srcPath: string, destinationPath: string, mode?: number): boolean {
try {
fs.copyFileSync(srcPath, destinationPath, mode);
return FsUtil.isExistSyncByPath(destinationPath);
} catch (error) {
Logger.i(FsUtil.TAG, 'copyFileSyncByPath err: ' + JSON.stringify(error));
return false;
}
}
/**
* 判断文件是否存在
*
* @param path 文件全目录
* @returns true:文件存在
*/
public static isExistSyncByPath(path: string): boolean {
try {
return fs.accessSync(path);
} catch (error) {
Logger.i(FsUtil.TAG, 'isExistSyncByPath error = ' + JSON.stringify(error));
return false;
}
}
/**
* 重命名异步接口
* @param oldPath 即将要重命名的文件全路径
* @param newPath 重命名之后的文件全路径
* @returns true:命名成功
*/
public static renameSyncByPath(oldPath: string, newPath: string): boolean {
try {
fs.renameSync(oldPath, newPath);
return FsUtil.isExistSyncByPath(newPath);
} catch (error) {
Logger.i(FsUtil.TAG, 'fs renameSync error = ' + JSON.stringify(error));
return false;
}
}
/**
* 同步获取文件大小
* @param path 文件全路径
* @returns 文件夹大小
*/
public static getFileSizeSyncByPath(path: string): number {
let size = 0;
try {
let stat = fs.statSync(path);
size = stat.size;
} catch (error) {
Logger.i(FsUtil.TAG, 'getFileInfoByFs fail, error:' + JSON.stringify(error));
}
return size;
}
}
@@ -24,7 +24,8 @@ import { MimeType } from './MimeType';
import { THUMBNAIL_SIZE } from '../../base/constants/UiConstant';
import { BasicDataSource } from './BasicDataSource';
import Logger from '../../base/log/Logger';
import AbilityCommonUtil from '../../base/utils/AbilityCommonUtil';
import AbilityCommonUtil, { ResultCodePicker } from '../../base/utils/AbilityCommonUtil';
import { StartModeOptions } from '../../base/model/StartModeOptions';
const TAG = 'FileAssetModel';
@@ -175,8 +176,8 @@ export class FileAssetModel {
}
}
pickFile(): void {
AbilityCommonUtil.terminateFilePicker([this.uri], [this.fileName]);
pickFile(startModeOptions: StartModeOptions): void {
AbilityCommonUtil.terminateFilePicker([this.uri], ResultCodePicker.SUCCESS, startModeOptions);
}
}
@@ -19,9 +19,10 @@ import fileAccess from '@ohos.file.fileAccess';
import { THUMBNAIL_SIZE } from '../../base/constants/UiConstant';
import { MimeType } from './MimeType';
import { BasicDataSource } from './BasicDataSource';
import AbilityCommonUtil from '../../base/utils/AbilityCommonUtil';
import AbilityCommonUtil, { ResultCodePicker } from '../../base/utils/AbilityCommonUtil';
import { FileUtil } from '../../base/utils/FileUtil';
import { ArrayUtil } from '../../base/utils/ArrayUtil';
import { StartModeOptions } from '../../base/model/StartModeOptions';
export class BreadData {
public title: string;
@@ -187,8 +188,8 @@ export class FilesData {
}
}
pickFile(): void {
AbilityCommonUtil.terminateFilePicker([this.uri], [this.fileName]);
pickFile(startModeOptions: StartModeOptions): void {
AbilityCommonUtil.terminateFilePicker([this.uri], ResultCodePicker.SUCCESS, startModeOptions);
}
setSubFolderList(subFolderList: FilesData[]) {
@@ -0,0 +1,132 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*/
import UIExtensionAbility from '@ohos.app.ability.UIExtensionAbility';
import { BusinessError } from '@kit.BasicServicesKit';
import type Want from '@ohos.app.ability.Want';
import dataPreferences from '@ohos.data.preferences';
import Logger from '../base/log/Logger';
import { StartModeOptions } from '../base/model/StartModeOptions';
import { PickerWindowType } from '../base/constants/FilePickerItems';
import { UIExtensionContentSession } from '@kit.AbilityKit';
import AbilityCommonUtil from '../base/utils/AbilityCommonUtil';
import { FilePickerUtil } from '../base/utils/FilePickerUtil';
import bundleResourceManager from '@ohos.bundle.bundleResourceManager';
const TRANSPARENT_COLOR = '#00000000';
const TAG: string = 'FilePickerUIExtAbility';
export default class FilePickerUIExtAbility extends UIExtensionAbility {
private abilityKey: string = '';
private securityPreferences?: dataPreferences.Preferences;
private storage: LocalStorage = new LocalStorage();
onCreate(): void {
Logger.i(TAG, 'FilePickerUIExtAbility onCreate');
}
async onSessionCreate(want: Want, session: UIExtensionContentSession): Promise<void> {
Logger.i(TAG, 'FilePickerUIExtAbility onSessionCreate, want: ' + JSON.stringify(want));
globalThis.abilityContext = this.context;
let options = this.initParam(want, session);
this.getAppResourceInfo(options.callerBundleName, this.storage);
if (options.isDownloadMode()) {
this.initSessionDownloadAuth(session);
return;
}
this.abilityKey = `${TAG}+${Date.now()}`;
if (options.isOpenFileMode()) {
this.initSessionFilePicker(session);
return;
}
if (options.isCreateFileMode()) {
this.initSessionPathPicker(session);
return;
}
}
onSessionDestroy(session: UIExtensionContentSession): void {
Logger.i(TAG, 'FilePickerUIExtAbility onSessionDestroy');
}
onDestroy(): void {
Logger.i(TAG, 'FilePickerUIExtAbility onDestroy');
}
private getAppResourceInfo(bundleName: string, storage: LocalStorage): void {
Logger.i(TAG, `getAppResourceInfo start`)
const bundleFlags = bundleResourceManager.ResourceFlag.GET_RESOURCE_INFO_ALL;
try {
const resourceInfo = bundleResourceManager.getBundleResourceInfo(bundleName, bundleFlags);
storage.setOrCreate<string>('appName', resourceInfo.label);
storage.setOrCreate<string>('appIcon', resourceInfo.icon);
} catch (err) {
const message = (err as BusinessError).message;
Logger.e(TAG, 'getBundleResourceInfo failed: %{public}s' + message);
}
}
initParam(want: Want, session: UIExtensionContentSession): StartModeOptions {
let options: StartModeOptions = FilePickerUtil.getStartModeOptions(want);
options.windowType = PickerWindowType.UI;
options.setUiExtContext(this.context);
options.context = this.context;
options.session = session;
options.fileSuffixFilters = AbilityCommonUtil.getKeyFileSuffixFilter(options.fileSuffixFilters);
if (options.isOpenFileMode()) {
options.fileSuffixFilters = AbilityCommonUtil.getKeyFileSuffixFilter(options.fileSuffixFilters);
options.phonePickerType = (want.parameters?.key_pick_type as string) || '';
options.phonePickerTypeList = AbilityCommonUtil.getKeyPickTypeList(want.parameters?.key_picker_type as object,
want.parameters?.key_picker_type_list as object)
}
if (options.isCreateFileMode()) {
options.PhoneFileSuffixChoices = AbilityCommonUtil.getKeyFileSuffixChoices(options.fileSuffixChoices);
}
this.storage.setOrCreate<StartModeOptions>('startModeOptions', options);
return options;
}
private initSessionDownloadAuth(session: UIExtensionContentSession) {
Logger.i(TAG, `initSessionDownloadAuth start`)
session.loadContent('pages/DownloadAuth', this.storage);
session.setWindowBackgroundColor(TRANSPARENT_COLOR);
}
private initSessionFilePicker(session: UIExtensionContentSession) {
Logger.i(TAG, `initSessionFilePicker start`)
try {
const promise = dataPreferences.getPreferences(this.context, 'securityWarning');
promise.then(async (object) => {
this.securityPreferences = object;
Logger.i(TAG, 'Succeeded in getting preferences.');
const hasSecurityWarning = this.securityPreferences.hasSync('securityWarning');
if (!hasSecurityWarning) {
await this.securityPreferences.put('securityWarning', '1');
await this.securityPreferences.flush();
this.storage?.setOrCreate('securityWarning', '1');
Logger.i(TAG, 'dataPreferences.flush');
}
}).catch((err: BusinessError) => {
console.error('Failed to get preferences. code =' + err.code + ', message =' + err.message);
})
} catch (err) {
console.error('Failed to get preferences. code =' + err.code + ', message =' + err.message);
}
AbilityCommonUtil.init().then(() => {
session.loadContent('pages/browser/storage/MyPhone', this.storage);
session.setWindowBackgroundColor(TRANSPARENT_COLOR);
});
}
private initSessionPathPicker(session: UIExtensionContentSession) {
Logger.i(TAG, `initSessionPathPicker start`)
AbilityCommonUtil.init().then(() => {
session.loadContent('pages/PathPicker', this.storage);
session.setWindowBackgroundColor(TRANSPARENT_COLOR);
});
}
};
@@ -0,0 +1,100 @@
/*
* 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 UIAbility from '@ohos.app.ability.UIAbility'
import window from '@ohos.window'
import AbilityCommonUtil from '../base/utils/AbilityCommonUtil'
import Logger from '../base/log/Logger'
import { FilePickerUtil } from '../base/utils/FilePickerUtil'
import { StartModeOptions } from '../base/model/StartModeOptions'
import { PickerWindowType } from '../base/constants/FilePickerItems'
import Want from '@ohos.app.ability.Want'
import { AbilityConstant } from '@kit.AbilityKit'
const TAG = 'MainAbility'
export default class MainAbility extends UIAbility {
private storage: LocalStorage = new LocalStorage();
private startModeOptions?: StartModeOptions;
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
Logger.i(TAG, 'onCreate')
globalThis.abilityContext = this.context;
let options: StartModeOptions = FilePickerUtil.getStartModeOptions(want);
options.windowType = PickerWindowType.ABILITY;
options.uiContext = this.context;
options.context = this.context;
options.fileSuffixFilters = AbilityCommonUtil.getKeyFileSuffixFilter(options.fileSuffixFilters);
if (options.isOpenFileMode()) {
options.fileSuffixFilters = AbilityCommonUtil.getKeyFileSuffixFilter(options.fileSuffixFilters);
options.phonePickerType = (want.parameters?.key_pick_type as string) || '';
options.phonePickerTypeList = AbilityCommonUtil.getKeyPickTypeList(want.parameters?.key_picker_type as object,
want.parameters?.key_picker_type_list as object)
}
if (options.isCreateFileMode()) {
options.PhoneFileSuffixChoices = AbilityCommonUtil.getKeyFileSuffixChoices(options.fileSuffixChoices);
}
this.startModeOptions = options;
this.storage.setOrCreate<StartModeOptions>('startModeOptions', options);
}
onDestroy() {
Logger.i(TAG, 'onDestroy')
AbilityCommonUtil.releaseMediaLibrary()
}
onWindowStageCreate(windowStage: window.WindowStage) {
// Main window is created, set main page for this ability
Logger.i(TAG, 'onWindowStageCreate')
AbilityCommonUtil.init().then(() => {
globalThis.windowClass = windowStage.getMainWindowSync();
if (this.startModeOptions?.isOpenFileMode()) {
// 文件选择器
windowStage.loadContent('pages/browser/storage/MyPhone', this.storage, (err, data) => {
if (err.code) {
Logger.e(TAG, 'Failed to load the content: ' + JSON.stringify(err));
return
}
Logger.i(TAG, 'data: ' + JSON.stringify(data));
})
} else {
// 路径选择器
windowStage.loadContent('pages/PathPicker', this.storage, (err, data) => {
if (err.code) {
Logger.e(TAG, 'Failed to load the content. Cause: ' + JSON.stringify(err))
return;
}
Logger.i(TAG, 'Succeeded in loading the content. Data: ' + JSON.stringify(data))
})
}
})
}
onWindowStageDestroy() {
// Main window is destroyed, release UI related resources
Logger.i(TAG, 'onWindowStageDestroy')
}
onForeground() {
// Ability has brought to foreground
Logger.i(TAG, 'onForeground')
}
onBackground() {
// Ability has back to background
Logger.i(TAG, 'onBackground')
}
}
@@ -1,119 +0,0 @@
/*
* 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 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'));
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.filePickNum = AbilityCommonUtil.getPickFileNum(parameters.key_pick_num);
// 文件选择范围:0-本地 1-云盘 不传则默认展示全部路径(未实现)
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
// 保存文件时的文件名列表
globalThis.keyPickFileName = parameters.key_pick_file_name || globalThis.saveFile || []
// 保存位置,[本地、云盘](未实现)
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))
}
onDestroy() {
Logger.i(TAG, 'onDestroy')
AbilityCommonUtil.releaseMediaLibrary()
}
onWindowStageCreate(windowStage: window.WindowStage) {
// Main window is created, set main page for this ability
Logger.i(TAG, 'onWindowStageCreate')
AbilityCommonUtil.init().then(() => {
globalThis.windowClass = windowStage.getMainWindowSync();
if (globalThis.action == 'ohos.want.action.OPEN_FILE' || globalThis.startMode == 'choose') {
// 文件选择器
windowStage.loadContent('pages/browser/storage/MyPhone', (err, data) => {
if (err.code) {
Logger.e(TAG, 'Failed to load the content: ' + JSON.stringify(err));
return
}
Logger.i(TAG, 'data: ' + JSON.stringify(data));
})
} else {
// 路径选择器
windowStage.loadContent('pages/PathPicker', (err, data) => {
if (err.code) {
Logger.e(TAG, 'Failed to load the content. Cause: ' + JSON.stringify(err))
return;
}
Logger.i(TAG, 'Succeeded in loading the content. Data: ' + JSON.stringify(data))
})
}
})
}
onWindowStageDestroy() {
// Main window is destroyed, release UI related resources
Logger.i(TAG, 'onWindowStageDestroy')
}
onForeground() {
// Ability has brought to foreground
Logger.i(TAG, 'onForeground')
}
onBackground() {
// Ability has back to background
Logger.i(TAG, 'onBackground')
}
}
+106
View File
@@ -0,0 +1,106 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
*/
import UIExtensionContentSession from '@ohos.app.ability.UIExtensionContentSession';
import { ability, wantConstant } from '@kit.AbilityKit';
import bundleManager from '@ohos.bundle.bundleManager';
import { DownloadDialog } from './component/dialog/DownloadDialog';
import { StartModeOptions } from '../base/model/StartModeOptions';
import { FilePickerUtil } from '../base/utils/FilePickerUtil';
import Logger from '../base/log/Logger';
import { VirtualUri } from '../base/constants/FolderRecord';
import { FsUtil } from '../base/utils/FsUtil';
import { FileUtil } from '../base/utils/FileUtil';
import { BusinessError } from '@kit.BasicServicesKit';
import uriPermissionManager from '@ohos.application.uriPermissionManager';
import AbilityCommonUtil from '../base/utils/AbilityCommonUtil';
const TAG = 'DownloadAuth';
const DOWNLOAD_PATH = '/storage/Users/currentUser/Download';
let storage = LocalStorage.getShared();
@Component
@Entry(storage)
struct DownloadAuth {
private downloadNewUri: string = '';
private startModeOptions: StartModeOptions = FilePickerUtil.getStartOptionsFromStorage();
private session: UIExtensionContentSession = this.startModeOptions.session;
// 存储当前从bms查询到的所有应用的bundle信息
public bundleArray: Array<bundleManager.BundleInfo> = [];
private appName: string | undefined = storage.get<string>('appName');
private appIcon: string | undefined = storage.get<string>('appIcon');
private confirm: Function = () => {
};
// 弹窗关联取消
private cancel: Function = () => {
};
aboutToAppear(): void {
Logger.i(TAG, 'DownloadAuth aboutToAppear');
}
onPageShow() {
Logger.i(TAG, 'DownloadAuth onPageShow appName: ' + this.appName + ', appIcon: ' + this.appIcon);
this.downloadDialogOpen();
}
downloadDialogOpen(): void {
this.confirm = async () => {
this.downloadDialogConfirm();
this.externalDownloadDialog.close();
};
this.cancel = () => {
this.externalDownloadDialog.close();
if (this.session != undefined) {
this.session.terminateSelf();
}
};
this.externalDownloadDialog.open();
}
async downloadDialogConfirm(): Promise<void> {
const bundleName: string = this.startModeOptions.callerBundleName;
Logger.i(TAG, 'Download Dialog confirm.');
this.downloadNewUri = VirtualUri.DOWNLOAD + '/' + bundleName;
let isExist: boolean = FsUtil.accessSync(DOWNLOAD_PATH + '/' + bundleName);
if (!isExist) {
this.downloadNewUri = FileUtil.createFolderByFs(VirtualUri.DOWNLOAD, bundleName);
}
Logger.i(TAG, 'Download Dialog return uri is: ' + this.downloadNewUri);
AbilityCommonUtil.grantUriPermission([this.downloadNewUri], bundleName);
this.externalDownloadDialog.close();
if (this.session === undefined) {
Logger.i(TAG, `this.session is undefined`)
return;
}
Logger.i(TAG, 'Download Dialog exist close.');
let abilityResult: ability.AbilityResult = {
resultCode: (this.downloadNewUri === undefined) ? -1 : 0,
want: {
parameters: {
'downloadNewUri': this.downloadNewUri
}
}
};
this.session.terminateSelfWithResult(abilityResult, (error) => {
Logger.i(TAG, 'terminateSelfWithResult is called = ' + error?.code);
});
}
build() {
}
//外部调用下载弹窗
externalDownloadDialog: CustomDialogController = new CustomDialogController({
builder: DownloadDialog({
appName: this.appName,
appIcon: this.appIcon,
confirm: this.confirm,
cancel: this.cancel
}),
autoCancel: false,
customStyle: true,
alignment: DialogAlignment.Center, // 可设置dialog的对齐方式,设定显示在底部或中间等,默认为底部显示
})
}
+35 -10
View File
@@ -17,7 +17,7 @@ import { fileTree } from './component/dialog/FileMoveDialog';
import Logger from '../base/log/Logger';
import ErrorCodeConst from '../base//constants/ErrorCodeConst';
import { toast } from '../base/utils/Common';
import AbilityCommonUtil from '../base/utils/AbilityCommonUtil';
import AbilityCommonUtil, { ResultCodePicker } from '../base/utils/AbilityCommonUtil';
import { SYSTEM_BAR_COLOR } from '../base/constants/UiConstant';
import StringUtil from '../base/utils/StringUtil';
import { FileUtil } from '../base/utils/FileUtil';
@@ -26,30 +26,34 @@ import MediaLibrary from '@ohos.multimedia.mediaLibrary';
import fileAccess from '@ohos.file.fileAccess';
import { ArrayUtil } from '../base/utils/ArrayUtil';
import { UiUtil } from '../base/utils/UiUtil';
import { StartModeOptions } from '../base/model/StartModeOptions';
import { FilePickerUtil } from '../base/utils/FilePickerUtil';
const TAG = 'PathSelector';
let storage = LocalStorage.getShared();
@Entry
@Entry(storage)
@Component
struct PathSelector {
private startModeOptions: StartModeOptions = FilePickerUtil.getStartOptionsFromStorage();
@State createResultType: number = ErrorCodeConst.PICKER.NORMAL;
aboutToAppear() {
UiUtil.setWindowBackground(SYSTEM_BAR_COLOR.LIGHT_GRAY);
}
async saveFileCallback(res): Promise<void> {
async saveFileCallback(res, startModeOptions: StartModeOptions): Promise<void> {
if (res?.cancel) {
globalThis.pathAbilityContext.terminateSelf();
AbilityCommonUtil.terminatePathPicker([], ResultCodePicker.CANCEL, startModeOptions);
return;
} else {
let fileNameList = globalThis.keyPickFileName;
let fileNameList = this.startModeOptions.newFileNames;
// 保存单个文件时文件名可修改,需使用修改后的文件名来创建文件
if (fileNameList.length <= 1) {
fileNameList = [res.fileName];
}
this.saveFiles(res.selectUri, fileNameList).then((createdFileList) => {
AbilityCommonUtil.terminatePathPicker(createdFileList);
AbilityCommonUtil.terminatePathPicker(createdFileList, ResultCodePicker.SUCCESS, startModeOptions);
}).catch((err) => {
let errorMessage = '';
let errorCode = 0;
@@ -59,7 +63,7 @@ struct PathSelector {
errorMessage = 'Same name file already exists';
errorCode = ErrorCodeConst.PICKER.FILE_NAME_EXIST;
this.createResultType = errorCode;
const pathName = globalThis.keyPickFileName;
const pathName = startModeOptions.newFileNames;
let listLength: number = pathName.length;
if (listLength == 1) {
return;
@@ -75,7 +79,7 @@ struct PathSelector {
errorMessage = err.message ? err.message : err;
errorCode = ErrorCodeConst.PICKER.OTHER_ERROR;
}
AbilityCommonUtil.terminatePathPicker([], errorCode, errorMessage);
AbilityCommonUtil.terminatePathPicker([], errorCode, startModeOptions);
toast($r('app.string.save_file_fail'));
Logger.e(TAG, `path select error, errorCode: ${errorCode}, errorMessage: ${errorMessage}`);
})
@@ -182,7 +186,7 @@ struct PathSelector {
}
private async tryRenameFileOperate(fileAccessHelper: fileAccess.FileAccessHelper, fileName: string,
dirUri: string, renameCount: number, fileNameList: string[] = []): Promise<{
dirUri: string, renameCount: number, fileNameList: string[] = []): Promise<{
err,
uri
}> {
@@ -229,11 +233,32 @@ struct PathSelector {
}
build() {
if (this.startModeOptions.isUxt()) {
Column() {
}.bindSheet(true, this.mainContent(), {
height: SheetSize.FIT_CONTENT,
dragBar: false,
showClose: false,
preferType: SheetType.CENTER,
onAppear: () => {
},
shouldDismiss: () => {
this.startModeOptions.session.terminateSelf();
}
})
} else {
this.mainContent()
}
}
@Builder
mainContent() {
Row() {
fileTree({
startModeOptions: this.startModeOptions,
createFileFailType: $createResultType,
moveCallback: (e) => {
this.saveFileCallback(e);
this.saveFileCallback(e, this.startModeOptions);
}
})
}
@@ -28,20 +28,24 @@ import { Loading } from '../../component/common/Loading';
import { getMediaType, getDurationByUri } from '../../../databases/model/FileAssetModel';
import Logger from '../../../base/log/Logger';
import multimedia_image from '@ohos.multimedia.image';
import AbilityCommonUtil from '../../../base/utils/AbilityCommonUtil';
import AbilityCommonUtil, { ResultCodePicker } 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';
import { StartModeOptions } from '../../../base/model/StartModeOptions';
import { FilePickerUtil } from '../../../base/utils/FilePickerUtil';
const TAG = 'myPhone';
let storage = LocalStorage.getShared();
@Entry
@Entry(storage)
@Component
struct MyPhone {
/**
* 正在加载
*/
private startModeOptions: StartModeOptions = FilePickerUtil.getStartOptionsFromStorage();
@State isShowLoading: boolean = true;
/**
* 文件或文件夹数据
@@ -119,7 +123,7 @@ struct MyPhone {
}
if (Array.isArray(fileData)) {
isContinue = true;
for (let i = 0;i < fileData.length; i++) {
for (let i = 0; i < fileData.length; i++) {
let fileName: string = fileData[i].fileName;
let currentDir: string = FileUtil.getPathWithFileSplit(fileData[i].currentDir);
if (data.startsWith(currentDir)) {
@@ -149,7 +153,7 @@ struct MyPhone {
onPageShow() {
// 文件选择器并且是多选模式下详情返回不更新,避免原有多选被重置
if (globalThis.filePickerViewFlag && this.isMulti) {
if (this.isMulti) {
return;
}
setImmersion(false);
@@ -197,7 +201,7 @@ struct MyPhone {
backCallback(): void {
if (!this.isMulti) {
AbilityCommonUtil.terminateFilePicker([], [], AbilityCommonUtil.RESULT_CODE.CANCEL);
AbilityCommonUtil.terminateFilePicker([], ResultCodePicker.SUCCESS, this.startModeOptions);
} else {
this.initData();
}
@@ -228,7 +232,7 @@ struct MyPhone {
multimedia_image.createPixelMap(new ArrayBuffer(4096), { size: { height: 1, width: 2 } }).then((pixelMap) => {
})
this.setShowLoading(true);
let pickPath = this.getParams();
let pickPath = this.getParams(this.startModeOptions);
if (StringUtil.isEmpty(pickPath)) {
this.getRootListFile();
} else {
@@ -241,8 +245,8 @@ struct MyPhone {
this.fileMkdirDialog = null;
}
getParams(): string {
let defaultPickPath = globalThis.keyFileDefaultPickPath;
getParams(startModeOptions: StartModeOptions): string {
let defaultPickPath = startModeOptions.defaultFilePathUri;
if (!ObjectUtil.isNullOrUndefined(defaultPickPath)) {
return defaultPickPath;
}
@@ -294,7 +298,7 @@ struct MyPhone {
this.getRootListFile();
}
} else {
AbilityCommonUtil.terminateFilePicker([], [], AbilityCommonUtil.RESULT_CODE.CANCEL);
AbilityCommonUtil.terminateFilePicker([], ResultCodePicker.CANCEL, this.startModeOptions);
}
}
return true;
@@ -302,9 +306,30 @@ struct MyPhone {
}
build() {
if (this.startModeOptions.isUxt()) {
Column() {
}.bindSheet(true, this.mainContent(), {
height: SheetSize.FIT_CONTENT,
dragBar: false,
showClose: false,
preferType: SheetType.CENTER,
onAppear: () => {
},
shouldDismiss: () => {
this.startModeOptions.session.terminateSelf();
}
})
} else {
this.mainContent()
}
}
@Builder
mainContent() {
Column() {
// 头部导航
TopBar({
startModeOptions: this.startModeOptions,
title: getResourceString($r('app.string.myPhone')),
isMulti: this.isMulti,
selectAll: this.selectAll,
@@ -328,6 +353,7 @@ struct MyPhone {
if (!this.isShowLoading) {
// 文件列表
FilesList({
startModeOptions: this.startModeOptions,
fileListSource: $fileListSource,
direList: $direList,
isMulti: $isMulti,
@@ -73,7 +73,7 @@ export struct TreeItem {
}
private async getPickPathListFiles(dirUri: string, expandPath: string, level: number): Promise<FileBase[]> {
let fileHelper = await FileUtil.getFileAccessHelperAsync(globalThis.abilityContext);
let fileHelper = await FileUtil.getFileAccessHelperAsync(getContext());
let fileInfo: fileAccess.FileInfo = await FileUtil.getFileInfoByUri(dirUri, fileHelper);
if (ObjectUtil.isNullOrUndefined(fileInfo) || !FileUtil.isFolder(fileInfo.mode)) {
Logger.e(TAG, 'uri is not folder');
@@ -15,7 +15,9 @@
import context from '@ohos.app.ability.common';
import { renderSize } from '../../../base/utils/Tools';
import AbilityCommonUtil from '../../../base//utils/AbilityCommonUtil';
import AbilityCommonUtil, { ResultCodePicker } from '../../../base//utils/AbilityCommonUtil';
import { StartModeOptions } from '../../../base/model/StartModeOptions';
import { FilePickerUtil } from '../../../base/utils/FilePickerUtil';
@Styles
function pressedStyles() {
@@ -24,7 +26,7 @@ function pressedStyles() {
}
@Styles
function normalStyles () {
function normalStyles() {
.borderRadius($r('app.float.common_borderRadius8'))
.backgroundColor($r('app.color.transparent_color'))
}
@@ -35,10 +37,9 @@ function subtitleStyles(fontSize: Resource) {
.alignSelf(ItemAlign.Start)
}
const filePickerViewFlag = globalThis.filePickerViewFlag;
@Component
export struct TopBar {
private startModeOptions: StartModeOptions = FilePickerUtil.getStartOptionsFromStorage();
private title?: string = '';
private subtitle?: string = '';
private fileSize?: number = 0;
@@ -57,9 +58,10 @@ export struct TopBar {
}
initTitle(): string | Resource {
if (this.isMulti && !filePickerViewFlag) {
if (this.isMulti) {
return this.checkedNum === 0 ? $r('app.string.selected_none') : this.checkedNum === 1 ?
$r('app.string.selected_items_singular', this.checkedNum) : $r('app.string.selected_items_plural', this.checkedNum);
$r('app.string.selected_items_singular', this.checkedNum) :
$r('app.string.selected_items_plural', this.checkedNum);
} else {
return this.title;
}
@@ -67,7 +69,8 @@ export struct TopBar {
filePickerTitle() {
if (this.isMulti) {
return this.checkedNum === 0 ? $r('app.string.selected_none') : $r('app.string.selected', this.checkedNum, globalThis.filePickNum);
return this.checkedNum === 0 ? $r('app.string.selected_none') :
$r('app.string.selected', this.checkedNum, globalThis.filePickNum);
} else {
return this.title;
}
@@ -89,7 +92,7 @@ export struct TopBar {
}
const uriList = this.checkedList.map(item => item.uri);
const fileNameList = this.checkedList.map(item => item.fileName);
AbilityCommonUtil.terminateFilePicker(uriList, fileNameList);
AbilityCommonUtil.terminateFilePicker(uriList, ResultCodePicker.SUCCESS, this.startModeOptions);
}
build() {
@@ -115,7 +118,7 @@ export struct TopBar {
})
Column() {
Text(filePickerViewFlag ? this.filePickerTitle() : this.initTitle())
Text(this.filePickerTitle())
.fontColor($r('app.color.black'))
.fontSize($r('app.float.common_font_size20'))
.fontWeight(FontWeight.Medium)
@@ -136,49 +139,46 @@ export struct TopBar {
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
if (filePickerViewFlag) {
if (!this.isMulti) {
Column() {
Image($r('app.media.hidisk_cancel_normal'))
.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'),
top: $r('app.float.common_padding10'),
bottom: $r('app.float.common_padding10')
})
.stateStyles({
pressed: pressedStyles,
normal: normalStyles
})
.onClick(() => {
let abilityContext = getContext(this) as context.UIAbilityContext;
abilityContext.terminateSelf();
})
} else {
Column() {
Image($r('app.media.ic_ok'))
.objectFit(ImageFit.Contain)
.width($r('app.float.common_size24'))
.height($r('app.float.common_size24'))
}.padding({
left: $r('app.float.common_padding12'),
right: $r('app.float.common_padding12'),
top: $r('app.float.common_padding10'),
bottom: $r('app.float.common_padding10')
})
.stateStyles({
pressed: pressedStyles,
normal: normalStyles
})
.onClick(() => {
this.terminate();
})
.opacity(this.checkSelectedFileList() ? $r('app.float.common_opacity10') : $r('app.float.common_opacity4'))
}
if (!this.isMulti) {
Column() {
Image($r('app.media.hidisk_cancel_normal'))
.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'),
top: $r('app.float.common_padding10'),
bottom: $r('app.float.common_padding10')
})
.stateStyles({
pressed: pressedStyles,
normal: normalStyles
})
.onClick(() => {
AbilityCommonUtil.terminateFilePicker([], ResultCodePicker.CANCEL, this.startModeOptions);
})
} else {
Column() {
Image($r('app.media.ic_ok'))
.objectFit(ImageFit.Contain)
.width($r('app.float.common_size24'))
.height($r('app.float.common_size24'))
}.padding({
left: $r('app.float.common_padding12'),
right: $r('app.float.common_padding12'),
top: $r('app.float.common_padding10'),
bottom: $r('app.float.common_padding10')
})
.stateStyles({
pressed: pressedStyles,
normal: normalStyles
})
.onClick(() => {
this.terminate();
})
.opacity(this.checkSelectedFileList() ? $r('app.float.common_opacity10') : $r('app.float.common_opacity4'))
}
}
.height($r('app.float.common_mark_y50'))
@@ -0,0 +1,65 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
*/
@Styles
function pressStyles() {
.backgroundColor($r('sys.color.ohos_id_color_click_effect'))
}
@Styles
function normalStyles() {
.backgroundColor($r('sys.color.ohos_id_color_background_transparent'))
}
@Component
export struct DialogButton {
text?: Resource;
color: Resource | string = $r('sys.color.ohos_id_color_text_primary_activated');
bgColor: Resource | string = $r('sys.color.ohos_id_color_background_transparent');
@Prop isDisabled: boolean = false;
click?: Function;
compId?: Resource = $r('app.string.confirm');
build() {
Row() {
Row() {
Text(this.text)
.fontSize($r('sys.float.ohos_id_text_size_button1'))
.fontColor(this.color)
.fontWeight(FontWeight.Medium)
.textCase(TextCase.UpperCase)
}.height('100%')
.width('100%')
.justifyContent(FlexAlign.Center)
.onClick(() => {
if ((!this.click) || this.isDisabled) {
return
}
this.click()
})
}
.id(this.text === this.compId ? 'dialog_confirm' : 'dialog_cancel')
.enabled(!this.isDisabled)
.height(40)
.layoutWeight(1)
.backgroundColor(this.bgColor)
.stateStyles({
normal: normalStyles,
pressed: pressStyles
})
.opacity(this.isDisabled ? $r('app.float.common_opacity5') : $r('app.float.common_opacity10'))
.borderRadius($r('sys.float.ohos_id_corner_radius_dialog'))
}
}
@Component
export struct DialogButtonDivider {
build() {
Divider().vertical(true)
.margin({ left: 8, right: 8 })
.height($r('app.float.divider_height24'))
.color($r('sys.color.ohos_id_color_list_separator'))
}
}
@@ -0,0 +1,109 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved.
*/
import { DialogButton, DialogButtonDivider } from './DialogComponent';
import { display } from '@kit.ArkUI';
import Logger from '../../../base/log/Logger';
const TAG = 'DownloadDialog';
let storage = LocalStorage.getShared();
@CustomDialog
export struct DownloadDialog {
controller?: CustomDialogController;
cancel: Function = () => {
};
confirm: Function = () => {
};
appName: string | undefined = storage.get<string>('appName');
appIcon: string | undefined = storage.get<string>('appIcon');
downloadTips: Resource = $r('app.string.download_tips', this.appName, this.appName);
cancelButtonText: Resource = $r('app.string.cancel');
confirmButtonText: Resource = $r('app.string.agree');
@State contentAlign: ItemAlign = ItemAlign.Start;
@State dialogWidth: number = 328;
closeDialogBind: Function = () => this.closeDialog();
aboutToAppear(): void {
let px2VpScale: number = px2vp(1);
let screenWidth: number = display.getDefaultDisplaySync()?.width ?
px2vp(display.getDefaultDisplaySync()?.width) : px2vp(display.getDefaultDisplaySync()?.width * px2VpScale);
this.dialogWidth = screenWidth > 432 ? 400 : screenWidth - 32;
}
closeDialog(): void {
if (this.controller) {
Logger.i(TAG, 'DownloadDialog close.');
this.controller.close();
}
}
build() {
Column() {
Column() {
this.downloadDialogContent();
}
.padding({
left: $r('app.float.common_padding24'),
right: $r('app.float.common_padding24')
})
}
.borderRadius($r('sys.float.ohos_id_corner_radius_dialog'))
.backgroundColor($r('sys.color.ohos_id_color_dialog_bg'))
.alignItems(HorizontalAlign.End)
.margin({ left: $r('app.float.common_margin16'), right: $r('app.float.common_margin16') })
.width(this.dialogWidth)
}
@Builder
downloadDialogContent() {
Row() {
Image(this.appIcon)
.fillColor($r('sys.color.ohos_id_color_primary'))
.objectFit(ImageFit.Contain)
.width($r('app.float.common_size64'))
.height($r('app.float.common_size64'))
.interpolation(ImageInterpolation.High)
.draggable(false)
.focusable(true)
.margin({ top: 24 })
.autoResize(false)
}
Row() {
Text(this.downloadTips)
.lineHeight(21)
.fontColor($r('sys.color.ohos_id_color_text_primary'))
.margin({ top: $r('app.float.common_margin16') })
.alignSelf(ItemAlign.Center)
}
Row() {
DialogButton({
text: this.cancelButtonText,
isDisabled: false,
color: '#0A56F7',
bgColor: '#F1F3F5',
click: () => {
this.cancel()
this.closeDialog()
}
})
DialogButtonDivider()
DialogButton({
text: this.confirmButtonText,
isDisabled: false,
color: '#0A56F7',
bgColor: '#F1F3F5',
click: () => {
this.confirm();
this.closeDialog();
}
})
}.width('100%')
.padding({ bottom: $r('app.float.common_margin10'), top: $r('app.float.common_margin10') })
.margin({
top: 8,
})
}
}
@@ -27,13 +27,17 @@ import { FileUtil } from '../../../base/utils/FileUtil';
import Logger from '../../../base/log/Logger';
import { ArrayUtil } from '../../../base/utils/ArrayUtil';
import ErrorCodeConst from '../../../base/constants/ErrorCodeConst';
import { StartModeOptions } from '../../../base/model/StartModeOptions';
import { FilePickerUtil } from '../../../base/utils/FilePickerUtil';
@Styles function pressedStyles () {
@Styles
function pressedStyles() {
.borderRadius($r('app.float.common_borderRadius8'))
.backgroundColor($r('app.color.hicloud_hmos_bg'))
}
@Styles function normalStyles() {
@Styles
function normalStyles() {
.borderRadius($r('app.float.common_borderRadius8'))
.backgroundColor($r('app.color.transparent_color'))
}
@@ -42,6 +46,7 @@ const TAG = 'fileTree';
@Component
export struct fileTree {
private startModeOptions: StartModeOptions = FilePickerUtil.getStartOptionsFromStorage();
@State listLength: number = 0;
@State positionY: string | number = '100%';
@State topRotate: boolean = false;
@@ -77,12 +82,12 @@ export struct fileTree {
async aboutToAppear() {
toast($r('app.string.select_location'))
const fileNameList = globalThis.keyPickFileName;
const fileNameList = this.startModeOptions.newFileNames;
this.listLength = fileNameList.length;
const fileName: string = fileNameList[0];
if (fileName) {
const dotIndex = fileName.lastIndexOf('.');
let fileSuffix = globalThis.keyFileSuffixChoices;
let fileSuffix = this.startModeOptions.PhoneFileSuffixChoices;
if (!StringUtil.isEmpty(fileSuffix)) {
this.suffix = fileSuffix;
if (dotIndex > 0) {
@@ -104,7 +109,7 @@ export struct fileTree {
if (globalThis.documentInfo) {
this.selectUri = globalThis.documentInfo.uri;
}
this.loadDefaultExpandPath();
this.loadDefaultExpandPath(this.startModeOptions);
}
aboutToDisappear() {
@@ -200,14 +205,14 @@ export struct fileTree {
/**
* 加载默认展开目录,如果是路径选择器拉起的,优先使用三方指定的目录
*/
async loadDefaultExpandPath() {
let defaultPickDir = globalThis.keyPathDefaultPickDir;
async loadDefaultExpandPath(startModeOptions: StartModeOptions) {
let defaultPickDir = startModeOptions.defaultFilePathUri;
let loadUri = this.lastSelectPath;
if (!StringUtil.isEmpty(defaultPickDir)) {
loadUri = defaultPickDir;
}
if (!StringUtil.isEmpty(loadUri)) {
let fileHelper = await FileUtil.getFileAccessHelperAsync(globalThis.abilityContext);
let fileHelper = await FileUtil.getFileAccessHelperAsync(startModeOptions.context);
let fileInfo = await FileUtil.getFileInfoByUri(loadUri, fileHelper);
if (fileInfo) {
this.defaultExpandPath = FileUtil.getCurrentFolderByFileInfo(fileInfo);
@@ -16,26 +16,29 @@
import { BreadData, FilesData, FileDataSource } from '../../../databases/model/FileData';
import { renderSize, gridName, isDlpFile } from '../../../base/utils/Tools';
import DateTimeUtil from '../../../base/utils/DateTimeUtil';
import { pickerStatus, filePickerTip } from '../../../base/utils/FilePickerUtil';
import { pickerStatus, filePickerTip, FilePickerUtil } from '../../../base/utils/FilePickerUtil';
import { Z_INDEX } from '../../../base/constants/UiConstant';
import Logger from '../../../base/log/Logger';
import { NoContent } from '../common/NoContent';
import { VideoDurationTag } from '../common/VideoDurationTag';
import { StartModeOptions } from '../../../base/model/StartModeOptions';
const TAG = 'MyPhone_FilesList';
const filePickerViewFlag = globalThis.filePickerViewFlag;
@Styles function pressedStyles() {
@Styles
function pressedStyles() {
.borderRadius($r('app.float.common_borderRadius8'))
.backgroundColor($r('app.color.hicloud_hmos_bg'))
}
@Styles function normalStyles() {
@Styles
function normalStyles() {
.borderRadius($r('app.float.common_borderRadius8'))
.backgroundColor($r('app.color.transparent_color'))
}
@Extend(Text) function grayText() {
@Extend(Text)
function grayText() {
.fontSize($r('app.float.common_font_size12'))
.fontColor($r('app.color.black'))
.lineHeight($r('app.float.common_size19'))
@@ -44,6 +47,7 @@ const filePickerViewFlag = globalThis.filePickerViewFlag;
@Component
export struct FilesList {
private startModeOptions: StartModeOptions = FilePickerUtil.getStartOptionsFromStorage();
@Link fileListSource: FileDataSource;
/**
* 面包屑
@@ -81,6 +85,7 @@ export struct FilesList {
LazyForEach(this.fileListSource, (item, index) => {
ListItem() {
FileListItem({
startModeOptions: this.startModeOptions,
fileItem: item,
fileListSource: $fileListSource,
direList: $direList,
@@ -100,6 +105,7 @@ export struct FilesList {
LazyForEach(this.fileListSource, (item, index) => {
GridItem() {
FileListItem({
startModeOptions: this.startModeOptions,
fileItem: item,
fileListSource: $fileListSource,
direList: $direList,
@@ -120,6 +126,7 @@ export struct FilesList {
@Component
struct FileListItem {
private startModeOptions: StartModeOptions = FilePickerUtil.getStartOptionsFromStorage();
@Consume isList: boolean;
@State fileItem: FilesData = new FilesData({});
@Link fileListSource: FileDataSource;
@@ -127,7 +134,7 @@ struct FileListItem {
@Link @Watch('isMultiChange') isMulti: boolean;
@Link checkedNum: number;
@State isChecked: boolean = false;
@State filePickerViewFlag: boolean = globalThis.filePickerViewFlag;
@State filePickerViewFlag: boolean = true;
@State isImageLoaded: boolean = true;
isShowThumbnail(item) {
@@ -148,7 +155,7 @@ struct FileListItem {
getRightIcon(item): Resource {
if (this.isMulti) {
if (pickerStatus(this.fileItem, this.checkedNum).differentTypes) {
if (pickerStatus(this.fileItem, this.checkedNum, this.startModeOptions).differentTypes) {
return;
}
return item.isChecked ? $r('app.media.checkbox_b') : $r('app.media.checkbox_g');
@@ -191,9 +198,9 @@ struct FileListItem {
if (this.fileItem.isFolder) {
return;
}
let status = pickerStatus(this.fileItem, this.checkedNum);
let status = pickerStatus(this.fileItem, this.checkedNum, this.startModeOptions);
if (status.exceedLimit) {
filePickerTip();
filePickerTip(this.startModeOptions);
return;
}
if (status.differentTypes) {
@@ -208,14 +215,14 @@ struct FileListItem {
onClickEvent() {
Logger.i(TAG, 'onClickEvent start');
let status = pickerStatus(this.fileItem, this.checkedNum);
let status = pickerStatus(this.fileItem, this.checkedNum, this.startModeOptions);
if (this.isMulti) {
if (this.fileItem.isFolder) {
return;
}
// 选择器页面,选择文件超出上限
if (status.exceedLimit) {
filePickerTip();
filePickerTip(this.startModeOptions);
return;
} else if (status.differentTypes) {
return;
@@ -238,7 +245,7 @@ struct FileListItem {
}
if (!status.exceedLimit && !status.differentTypes) {
// 选中的数据回调给三方应用
this.fileItem.pickFile();
this.fileItem.pickFile(this.startModeOptions);
return;
}
}
@@ -258,7 +265,7 @@ struct FileListItem {
}
calOpacity() {
const statusObj = pickerStatus(this.fileItem, this.checkedNum);
const statusObj = pickerStatus(this.fileItem, this.checkedNum, this.startModeOptions);
return statusObj.exceedLimit || (!this.fileItem.isFolder && statusObj.differentTypes) ||
(this.isMulti && this.fileItem.isFolder) ? $r('app.float.common_opacity2') : $r('app.float.common_opacity10');
}
@@ -332,8 +339,10 @@ struct FileListItem {
@Builder
buildGridItemView() {
Flex({ direction: FlexDirection.Column,
alignItems: ItemAlign.Center }) {
Flex({
direction: FlexDirection.Column,
alignItems: ItemAlign.Center
}) {
Column() {
Column() {
Image(this.isShowThumbnail(this.fileItem) ? this.fileItem.thumbUri : this.fileItem.localGridIcon)
+10 -1
View File
@@ -15,7 +15,7 @@
"abilities": [
{
"name": "MainAbility",
"srcEntry": "./ets/entryability/MainAbility.ts",
"srcEntry": "./ets/entryability/MainAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:app_icon",
"label": "$string:EntryAbility_label",
@@ -32,6 +32,15 @@
]
}
],
"extensionAbilities": [
{
"name": "FilePickerUIExtAbility",
"srcEntry": "./ets/entryability/FilePickerUIExtAbility.ets",
"description": "FilePickerUIExtAbility",
"exported": true,
"type": "sysPicker/filePicker",
},
],
requestPermissions: [
// 媒体库管理权限
{