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