1/* 2 * Copyright (c) 2021-2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { FilesData } from '../../databases/model/FileData' 17import { on } from '../../base/utils/EventBus' 18import FileAccessExec from '../../base/utils/FileAccessExec' 19import { TREE_LAYER } from '../../base/constants/UiConstant' 20import { FOLDER_LEVEL } from '../../base/constants/Constant' 21import { FileUtil } from '../../base/utils/FileUtil' 22import { ArrayUtil } from '../../base/utils/ArrayUtil' 23import { FileBase } from '../../databases/model/base/FileBase' 24import Logger from '../../base/log/Logger' 25import fileAccess from '@ohos.file.fileAccess' 26import ObjectUtil from '../../base/utils/ObjectUtil' 27 28@Styles function pressedStyles() { 29 .backgroundColor($r('app.color.hicloud_hmos_bg')) 30} 31 32const TAG = 'TreeItem'; 33 34@Component 35export struct TreeItem { 36 fileItem: FilesData = new FilesData({}) 37 loadPath?: string = ''; 38 isNeedExpand: boolean = false; 39 @State iconRotate: boolean = false 40 @State subFolderList: Array<FilesData> = new Array<FilesData>() 41 @Link chooseItem: FilesData 42 @Link selectUri: string 43 @Link selectName: string 44 @Link fileList: Array<FilesData> 45 @Link folderList: Array<FilesData> 46 @State isShowArrow: boolean = true 47 @Prop layer: number 48 @State isLoading: boolean = false; 49 @Link @Watch('clickExpandChange') isClickExpand: boolean; 50 51 private changeSelectItem(selectedItem: FilesData, autoShow: boolean) { 52 if (selectedItem) { 53 selectedItem.autoShow = autoShow; 54 this.chooseItem = selectedItem; 55 this.selectUri = this.chooseItem.uri; 56 this.selectName = this.chooseItem.fileName; 57 } 58 } 59 60 private async executeQuery(dirUri: string, defaultExpandPath: string, call: Function) { 61 this.isLoading = true; 62 if (!this.isNeedExpand || (this.isNeedExpand && !this.isClickExpand)) { 63 this.changeSelectItem(this.fileItem, false); 64 } 65 66 let queryRes = await this.getPickPathListFiles(dirUri, defaultExpandPath, this.fileItem.layer); 67 this.isLoading = false; 68 let subList: Array<FilesData> = this.fileBaseToFileData(queryRes); 69 let { folderList, fileList } = this.transfer(subList); 70 this.fileList = fileList 71 call(folderList); 72 } 73 74 private async getPickPathListFiles(dirUri: string, expandPath: string, level: number): Promise<Array<FileBase>> { 75 let fileHelper = await FileUtil.getFileAccessHelperAsync(globalThis.abilityContext); 76 let fileInfo: fileAccess.FileInfo = await FileUtil.getFileInfoByUri(dirUri, fileHelper); 77 if (ObjectUtil.isNullOrUndefined(fileInfo) || !FileUtil.isFolder(fileInfo.mode)) { 78 Logger.e(TAG, 'uri is not folder'); 79 return; 80 } 81 let queryRes = FileAccessExec.getPathPickSubFiles(fileInfo, expandPath, level); 82 if (ObjectUtil.isNull(queryRes)) { 83 Logger.e(TAG, 'files is null'); 84 return; 85 } 86 return queryRes; 87 } 88 89 transfer(list: FilesData[]) { 90 let folderList = new Array<FilesData>(); 91 let fileList = new Array<FilesData>(); 92 if (ArrayUtil.isEmpty(list)) { 93 return { folderList, fileList }; 94 } 95 for (let i = 0; i < list.length; i++) { 96 let fileData = list[i]; 97 if (fileData.isFolder) { 98 folderList.push(fileData); 99 } else { 100 fileList.push(fileData); 101 } 102 } 103 return { folderList, fileList }; 104 } 105 106 fileBaseToFileData(list: Array<FileBase>): Array<FilesData> { 107 let fileArray = new Array<FilesData>(); 108 if (ArrayUtil.isEmpty(list)) { 109 return fileArray; 110 } 111 for (let i = 0; i < list.length; i++) { 112 let data = list[i]; 113 let fileData = new FilesData([]); 114 fileData.uri = data.uri; 115 fileData.fileName = data.fileName; 116 fileData.isFolder = data.isFolder; 117 fileData.size = data.fileSize; 118 fileData.mtime = data.modifyTime; 119 fileData.path = data.relativePath; 120 fileData.currentDir = data.currentDir; 121 if (data.isFolder) { 122 if (!ArrayUtil.isEmpty(data.subList)) { 123 fileData.setSubList(this.fileBaseToFileData(data.subList)) 124 } 125 } 126 fileArray.push(fileData); 127 } 128 return fileArray; 129 } 130 131 /** 132 * 是否需要展开目录,如果最近保存的目录不为空,需要展开到最近保存的目录 133 * 134 * @returns true:需要展开目录 135 */ 136 needExpandPath(): boolean { 137 if (!this.canExpandPath() || this.isClickExpand) { 138 return false; 139 } 140 return FileUtil.hasSubFolder(this.loadPath, this.fileItem.currentDir); 141 } 142 143 clickExpandChange() { 144 this.isNeedExpand = false; 145 this.loadPath = ''; 146 } 147 148 canExpandPath(): boolean { 149 return this.layer <= FOLDER_LEVEL.MAX_LEVEL; 150 } 151 152 loadSubFolder(subFolderList: Array<FilesData>) { 153 this.subFolderList = subFolderList; 154 this.folderList = this.subFolderList; 155 this.fileItem.setSubFolderList(subFolderList); 156 Logger.i(TAG, "loadSubFolder:selectUri = " + this.selectUri + 157 " ; subFolderListSize = " + this.subFolderList.length + 158 " ; iconRotate = " + this.iconRotate); 159 } 160 161 aboutToAppear() { 162 on('fileMkdir', async (e) => { 163 if (this.selectUri === this.fileItem.uri) { 164 // 获取当前选中文件夹下的所有子文件 165 let queryArray = await this.getPickPathListFiles(this.fileItem.uri, "", this.fileItem.layer); 166 let subList: Array<FilesData> = this.fileBaseToFileData(queryArray); 167 let { folderList, fileList } = this.transfer(subList); 168 this.fileList = fileList 169 // 获取当前选中文件夹下的所有子文件 170 this.subFolderList = folderList; 171 this.expandSubFolderCall(folderList); 172 // 查找刚刚新建的文件夹index 173 const index = this.subFolderList.findIndex(item => item.fileName === e.mkdirName); 174 if (index !== -1 && this.canExpandPath()) { 175 // 默认选中刚刚新建的文件夹 176 this.changeSelectItem(this.subFolderList[index], true); 177 this.iconRotate = true; 178 this.fileList = []; 179 this.folderList = []; 180 } 181 } 182 }) 183 184 this.fileItem.setLayer(this.layer); 185 this.isNeedExpand = this.needExpandPath(); 186 if (this.isNeedExpand) { 187 Logger.i(TAG, "NeedExpand:loadPath = " + this.loadPath + 188 " ; path = " + this.fileItem.currentDir); 189 this.clickExpand(false); 190 } 191 } 192 193 clickExpand(forceLoading: boolean) { 194 if (!this.isLoading) { 195 if (this.iconRotate) { 196 this.iconRotate = !this.iconRotate; 197 this.changeSelectItem(this.fileItem, false); 198 this.fileItem.subFileList = null; 199 this.folderList = this.fileItem.subFolderList; 200 } else { 201 if (this.canExpandPath()) { 202 if (this.fileItem.hasSubFolderList() && !forceLoading) { 203 this.changeSelectItem(this.fileItem, false); 204 this.fileList = this.fileItem.subFileList; 205 this.expandSubFolderCall(this.fileItem.getSubFolderList()); 206 } else { 207 this.executeQuery(this.fileItem.uri, this.loadPath, this.expandSubFolderCall.bind(this)); 208 } 209 } 210 } 211 } 212 } 213 214 private expandSubFolderCall(subFolderList: Array<FilesData>) { 215 this.iconRotate = !this.iconRotate; 216 this.loadSubFolder(subFolderList); 217 this.isShowArrow = this.subFolderList.length !== 0; 218 } 219 220 build() { 221 Column() { 222 Row() { 223 Image($r('app.media.hidisk_ic_list_empty_folder')) 224 .objectFit(ImageFit.Contain) 225 .renderMode(ImageRenderMode.Original) 226 .aspectRatio(1) 227 .width($r('app.float.common_size24')) 228 .alignSelf(ItemAlign.Center) 229 .margin({ right: $r('app.float.common_margin16') }) 230 Text(this.fileItem.fileName) 231 .fontSize($r('app.float.common_font_size16')) 232 .layoutWeight(1) 233 .maxLines(1) 234 .textOverflow({ overflow: TextOverflow.Ellipsis }) 235 if (this.isLoading) { 236 LoadingProgress() 237 .width($r('app.float.common_size24')) 238 .height($r('app.float.common_size24')) 239 .color($r('sys.color.ohos_id_color_text_secondary')) 240 } else { 241 Image($r('app.media.ic_arrow_right')) 242 .objectFit(ImageFit.Contain) 243 .autoResize(true) 244 .height($r('app.float.common_size12')) 245 .width($r('app.float.common_size12')) 246 .interpolation(ImageInterpolation.Medium) 247 .rotate({ z: 90, angle: this.iconRotate ? 90 : 0 }) 248 .visibility(this.isShowArrow ? Visibility.Visible : Visibility.None) 249 } 250 } 251 .width('100%') 252 .padding({ 253 top: $r('app.float.common_padding16'), 254 bottom: $r('app.float.common_padding16'), 255 right: $r('app.float.common_padding24'), 256 left: this.layer * TREE_LAYER + 'vp' 257 }) 258 .backgroundColor(this.selectUri === this.fileItem.uri ? $r('app.color.move_dialog_background') : '') 259 .stateStyles({ 260 pressed: pressedStyles 261 }) 262 .onClick(() => { 263 this.isClickExpand = true; 264 this.clickExpand(true); 265 }) 266 267 if (this.subFolderList.length && this.iconRotate) { 268 ForEach(this.subFolderList, (item) => { 269 TreeItem({ 270 fileItem: item, 271 loadPath: this.loadPath, 272 chooseItem: $chooseItem, 273 selectUri: $selectUri, 274 selectName: $selectName, 275 layer: this.layer + 1, 276 folderList: $folderList, 277 fileList: $fileList, 278 isClickExpand: $isClickExpand 279 }) 280 }) 281 } 282 } 283 } 284}