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