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 FileAccessExec from '../../../base/utils/FileAccessExec'; 18import { toast, isValidFileName } from '../../../base/utils/Common'; 19import { TreeItem } from '../TreeItem'; 20import { FileMkdirDialog } from './FileMkdirDialog'; 21import { emit } from '../../../base/utils/EventBus'; 22import { getResourceString } from '../../../base/utils/Tools'; 23import { FILENAME_MAX_LENGTH, FILE_MANAGER_PREFERENCES, FOLDER_LEVEL } from '../../../base/constants/Constant'; 24import StringUtil from '../../../base/utils/StringUtil'; 25import { setPreferencesValue } from '../../../base/utils/PreferencesUtil'; 26import { FileUtil } from '../../../base/utils/FileUtil'; 27import Logger from '../../../base/log/Logger'; 28import { ArrayUtil } from '../../../base/utils/ArrayUtil'; 29import ErrorCodeConst from '../../../base/constants/ErrorCodeConst'; 30import { StartModeOptions } from '../../../base/model/StartModeOptions'; 31import { FilePickerUtil } from '../../../base/utils/FilePickerUtil'; 32 33@Styles 34function pressedStyles() { 35 .borderRadius($r('app.float.common_borderRadius8')) 36 .backgroundColor($r('app.color.hicloud_hmos_bg')) 37} 38 39@Styles 40function normalStyles() { 41 .borderRadius($r('app.float.common_borderRadius8')) 42 .backgroundColor($r('app.color.transparent_color')) 43} 44 45const TAG = 'fileTree'; 46 47@Component 48export struct fileTree { 49 private startModeOptions: StartModeOptions = FilePickerUtil.getStartOptionsFromStorage(); 50 @State listLength: number = 0; 51 @State topRotate: boolean = false; 52 @State rootData: FilesData[] = []; 53 @State selectUri: string = ''; 54 @State @Watch('nameChange') selectName: string = getResourceString($r('app.string.myPhone')); 55 public moveCallback: (e) => void; 56 @State @Watch('selectChange') chooseItem: FilesData = new FilesData({}); 57 @State folderList: FilesData[] = []; 58 @State fileList: FilesData[] = []; 59 @State changeTitle: Resource = undefined; 60 @State fileName: string = ''; 61 @State suffix: string = ''; 62 fileMkdirDialog: CustomDialogController = new CustomDialogController({ 63 builder: FileMkdirDialog({ 64 fileItems: this.folderList, 65 getCurrentDir: this.selectUri, 66 confirm: this.fileMkdir.bind(this) 67 }), 68 autoCancel: true, 69 alignment: DialogAlignment.Bottom, 70 offset: { dx: 0, dy: -80 } 71 }); 72 @State isSelectRootPath: boolean = true; 73 @State errorText: Resource = undefined; 74 @State isNeedLoadDefaultPath: boolean = false; 75 @State isClickExpand: boolean = false; 76 @Link @Watch('createFileFailTypeChange') createFileFailType: number; 77 lastSelectPath: string = AppStorage.Get<string>(FILE_MANAGER_PREFERENCES.lastSelectPath.key); 78 defaultExpandPath: string = ''; 79 scroller: Scroller = new Scroller(); 80 context: Context = globalThis.abilityContext; 81 82 async aboutToAppear() { 83 toast($r('app.string.select_location')) 84 const fileNameList = this.startModeOptions.newFileNames; 85 this.listLength = fileNameList.length; 86 const fileName: string = fileNameList[0]; 87 if (fileName) { 88 const dotIndex = fileName.lastIndexOf('.'); 89 let fileSuffix = this.startModeOptions.PhoneFileSuffixChoices; 90 if (!StringUtil.isEmpty(fileSuffix)) { 91 this.suffix = fileSuffix; 92 if (dotIndex > 0) { 93 this.fileName = fileName.substring(0, dotIndex) + fileSuffix; 94 } else { 95 this.fileName = fileName + fileSuffix; 96 } 97 } else { 98 this.fileName = fileName; 99 } 100 } 101 this.nameChange(); 102 this.isSelectRootPath = true; 103 let { folderList, fileList } = FileAccessExec.getFileData(); 104 this.fileList = fileList; 105 this.rootData = folderList; 106 this.folderList = this.rootData; 107 this.topRotate = !this.topRotate; 108 if (globalThis.documentInfo) { 109 this.selectUri = globalThis.documentInfo.uri; 110 } 111 this.loadDefaultExpandPath(this.startModeOptions); 112 } 113 114 aboutToDisappear() { 115 this.fileMkdirDialog = null; 116 } 117 118 fileMkdir(e) { 119 emit('fileMkdir', e) 120 if (this.isSelectRootPath) { 121 // 获取当前选中文件夹下的所有子文件 122 let { folderList, fileList } = FileAccessExec.getFileData(); 123 this.rootData = folderList; 124 // 查找刚刚新建的文件夹index 125 const index = this.rootData.findIndex(item => item.fileName === e.mkdirName); 126 if (index !== -1) { 127 // 默认选中刚刚新建的文件夹 128 this.selectUri = this.rootData[index].uri; 129 this.selectName = this.rootData[index].fileName; 130 this.topRotate = true; 131 this.fileList = []; 132 this.folderList = []; 133 } else { 134 this.fileList = fileList; 135 this.folderList = folderList; 136 } 137 } 138 } 139 140 nameChange() { 141 this.changeTitle = this.listLength <= 1 ? 142 $r('app.string.to_save', this.selectName) : $r('app.string.to_save_plural', this.listLength, this.selectName) 143 if (this.isSelectRootPath) { 144 this.isSelectRootPath = false; 145 } 146 } 147 148 createFileFailTypeChange() { 149 if (this.createFileFailType === ErrorCodeConst.PICKER.FILE_NAME_EXIST) { 150 this.errorText = $r('app.string.save_file_has_same_file'); 151 this.createFileFailType = ErrorCodeConst.PICKER.NORMAL; 152 } 153 } 154 155 selectChange() { 156 let autoShow: boolean = false; 157 if (this.chooseItem) { 158 autoShow = this.chooseItem.autoShow; 159 this.chooseItem.autoShow = false; 160 } 161 if (!this.isClickExpand || autoShow) { 162 let loadSubFinish = FileUtil.loadSubFinish(this.defaultExpandPath, 163 this.chooseItem.currentDir, FOLDER_LEVEL.MAX_LEVEL - 2); 164 if (loadSubFinish || autoShow) { 165 let allData: FilesData[] = []; 166 let pos = this.getSelectItemPos(this.rootData, allData); 167 let itemHeight: number = this.context.resourceManager.getNumber($r('app.float.common_size56')); 168 let scrollY: number = itemHeight * (pos - 1); 169 Logger.i(TAG, 'selectItemPos = ' + pos + ',itemHeight = ' + itemHeight + ' ; scrollY = ' + scrollY); 170 setTimeout(() => { 171 if (scrollY < 0) { 172 this.scroller.scrollEdge(Edge.Start); 173 } else { 174 this.scroller.scrollTo({ xOffset: 0, yOffset: scrollY }); 175 } 176 }, 0); 177 } 178 } 179 } 180 181 private getSelectItemPos(fileList: FilesData[], allData: FilesData[]): number { 182 if (ArrayUtil.isEmpty(allData)) { 183 allData = []; 184 } 185 if (!ArrayUtil.isEmpty(fileList)) { 186 for (let index = 0; index < fileList.length; index++) { 187 const fileData: FilesData = fileList[index]; 188 allData.push(fileData); 189 if (fileData.uri === this.selectUri) { 190 return allData.length; 191 } 192 if (fileData.hasSubFolderList()) { 193 let subFolderList: FilesData[] = fileData.getSubFolderList(); 194 let result = this.getSelectItemPos(subFolderList, allData); 195 if (result > 0) { 196 return result; 197 } 198 } 199 } 200 } 201 return 0; 202 } 203 204 /** 205 * 加载默认展开目录,如果是路径选择器拉起的,优先使用三方指定的目录 206 */ 207 async loadDefaultExpandPath(startModeOptions: StartModeOptions) { 208 let defaultPickDir = startModeOptions.defaultFilePathUri; 209 let loadUri = this.lastSelectPath; 210 if (!StringUtil.isEmpty(defaultPickDir)) { 211 loadUri = defaultPickDir; 212 } 213 if (!StringUtil.isEmpty(loadUri)) { 214 let fileHelper = await FileUtil.getFileAccessHelperAsync(startModeOptions.context); 215 let fileInfo = await FileUtil.getFileInfoByUri(loadUri, fileHelper); 216 if (fileInfo) { 217 this.defaultExpandPath = FileUtil.getCurrentFolderByFileInfo(fileInfo); 218 Logger.i(TAG, 'loadDefaultExpandPath = ' + this.defaultExpandPath); 219 // 值为true,说明需要刷新树布局,并且传入loadPath 220 this.isNeedLoadDefaultPath = !StringUtil.isEmpty(this.defaultExpandPath); 221 } 222 } 223 } 224 225 private canCreateFolder(): boolean { 226 if (this.chooseItem && this.chooseItem.layer) { 227 return this.chooseItem.layer < FOLDER_LEVEL.MAX_LEVEL; 228 } 229 return true; 230 } 231 232 build() { 233 Column() { 234 Row() { 235 Image($r('app.media.hidisk_cancel_normal')) 236 .width($r('app.float.common_size46')) 237 .height($r('app.float.common_size46')) 238 .objectFit(ImageFit.Contain) 239 .padding($r('app.float.common_padding10')) 240 .stateStyles({ 241 pressed: pressedStyles, 242 normal: normalStyles 243 }) 244 .interpolation(ImageInterpolation.Medium) 245 .onClick(() => { 246 this.moveCallback.call(this, { 247 cancel: true 248 }); 249 }) 250 Blank() 251 Image($r('app.media.hidisk_ic_add_folder')) 252 .width($r('app.float.common_size46')) 253 .height($r('app.float.common_size46')) 254 .objectFit(ImageFit.Contain) 255 .margin({ left: $r('app.float.common_margin2') }) 256 .padding($r('app.float.common_padding10')) 257 .stateStyles({ 258 pressed: pressedStyles, 259 normal: normalStyles 260 }) 261 .enabled(this.canCreateFolder()) 262 .opacity(this.canCreateFolder() ? $r('app.float.common_opacity10') : $r('app.float.common_opacity2')) 263 .interpolation(ImageInterpolation.Medium) 264 .onClick(() => { 265 this.fileMkdirDialog.open(); 266 }) 267 Image($r('app.media.ic_ok')) 268 .width($r('app.float.common_size46')) 269 .height($r('app.float.common_size46')) 270 .objectFit(ImageFit.Contain) 271 .objectFit(ImageFit.Contain) 272 .margin({ left: $r('app.float.common_margin2') }) 273 .padding($r('app.float.common_padding10')) 274 .stateStyles({ 275 pressed: pressedStyles, 276 normal: normalStyles 277 }) 278 .onClick(async () => { 279 setPreferencesValue(FILE_MANAGER_PREFERENCES.name, 280 FILE_MANAGER_PREFERENCES.lastSelectPath.key, this.selectUri); 281 AppStorage.SetOrCreate<string>(FILE_MANAGER_PREFERENCES.lastSelectPath.key, this.selectUri); 282 const prefix = this.fileName.trim(); 283 if (!prefix) { 284 this.errorText = $r('app.string.input_nothing'); 285 return; 286 } 287 const fileName = this.fileName.trim(); 288 if (StringUtil.getBytesCount(fileName) > FILENAME_MAX_LENGTH) { 289 this.errorText = $r('app.string.max_input_length'); 290 } else if (!isValidFileName(fileName)) { 291 this.errorText = $r('app.string.input_invalid'); 292 } else { 293 this.errorText = null; 294 this.moveCallback({ 295 selectUri: this.selectUri, 296 fileName: fileName 297 }); 298 } 299 }) 300 }.width('100%') 301 .padding({ 302 top: $r('app.float.common_padding5'), 303 right: $r('app.float.common_padding15'), 304 bottom: $r('app.float.common_padding30'), 305 left: $r('app.float.common_padding15') 306 }) 307 308 Row() { 309 if (this.listLength > 1) { 310 Image($r('app.media.hidisk_icon_unknown')) 311 .objectFit(ImageFit.Contain) 312 .renderMode(ImageRenderMode.Original) 313 .aspectRatio(1) 314 .width($r('app.float.common_size52')) 315 .height($r('app.float.common_size52')) 316 .alignSelf(ItemAlign.Center) 317 .margin({ right: $r('app.float.common_margin10') }) 318 .borderRadius($r('app.float.common_borderRadius8')) 319 } 320 Column() { 321 Text(this.changeTitle) 322 .fontSize($r('app.float.common_font_size16')) 323 .maxLines(1) 324 .textOverflow({ overflow: TextOverflow.Ellipsis }) 325 } 326 .layoutWeight(1).alignItems(HorizontalAlign.Start) 327 } 328 .padding({ 329 right: $r('app.float.common_padding15'), 330 left: $r('app.float.common_padding15'), 331 bottom: $r('app.float.common_padding15') 332 }) 333 334 if (this.listLength <= 1) { 335 Column() { 336 TextInput({ text: this.fileName }) 337 .fontSize($r('app.float.common_font_size16')) 338 .backgroundColor($r('app.color.text_input_bg_color')) 339 .onChange((newVal) => { 340 this.fileName = newVal 341 this.errorText = null 342 }) 343 Divider().vertical(false).strokeWidth(1).color(Color.Gray) 344 .margin({ 345 left: $r('app.float.common_margin20'), 346 right: $r('app.float.common_margin20'), 347 bottom: $r('app.float.common_margin2') 348 }) 349 350 Text(this.errorText) 351 .margin({ 352 left: $r('app.float.common_margin20'), 353 right: $r('app.float.common_margin20') 354 }) 355 .padding({ 356 top: $r('app.float.common_padding5'), 357 bottom: $r('app.float.common_padding10') 358 }) 359 .fontSize($r('app.float.common_font_size14')) 360 .fontColor($r('app.color.error_message_color')) 361 .alignSelf(ItemAlign.Start) 362 } 363 .margin({ bottom: $r('app.float.common_size10') }) 364 } 365 366 Row().width('100%').height($r('app.float.common_size4')).opacity(0.05).backgroundColor($r('app.color.black')) 367 368 Row() { 369 Image($r('app.media.hidisk_ic_classify_phone')) 370 .objectFit(ImageFit.Contain) 371 .renderMode(ImageRenderMode.Original) 372 .aspectRatio(1) 373 .width($r('app.float.common_size24')) 374 .alignSelf(ItemAlign.Center) 375 .margin({ right: $r('app.float.common_margin16') }) 376 Text($r('app.string.myPhone')) 377 .fontSize($r('app.float.common_font_size16')) 378 .layoutWeight(1) 379 Image($r('app.media.ic_arrow_right')) 380 .objectFit(ImageFit.Contain) 381 .autoResize(true) 382 .height($r('app.float.common_size12')) 383 .width($r('app.float.common_size12')) 384 .interpolation(ImageInterpolation.Medium) 385 .rotate({ z: 90, angle: this.topRotate ? 90 : 0 }) 386 } 387 .width('100%') 388 .padding({ 389 top: $r('app.float.common_padding16'), 390 bottom: $r('app.float.common_padding16'), 391 left: $r('app.float.common_padding24'), 392 right: $r('app.float.common_padding24') 393 }) 394 .backgroundColor(this.isSelectRootPath ? $r('app.color.path_pick_selected_bg') : '') 395 .onClick(async () => { 396 this.selectName = getResourceString($r('app.string.myPhone')); 397 this.selectUri = globalThis.documentInfo && globalThis.documentInfo?.uri; 398 this.isSelectRootPath = true; 399 let { folderList, fileList } = await FileAccessExec.getFileData(); 400 this.topRotate = !this.topRotate; 401 this.fileList = fileList; 402 this.rootData = folderList; 403 this.folderList = this.rootData; 404 this.isClickExpand = true; 405 this.defaultExpandPath = ''; 406 }) 407 408 Scroll(this.scroller) { 409 Column() { 410 if (this.rootData.length && this.topRotate) { 411 ForEach(this.rootData, (item) => { 412 if (this.isNeedLoadDefaultPath) { 413 TreeItem({ 414 fileItem: item, 415 loadPath: this.defaultExpandPath, 416 selectUri: $selectUri, 417 chooseItem: $chooseItem, 418 selectName: $selectName, 419 layer: 2, 420 folderList: $folderList, 421 fileList: $fileList, 422 isClickExpand: $isClickExpand 423 }) 424 } else { 425 TreeItem({ 426 fileItem: item, 427 selectUri: $selectUri, 428 chooseItem: $chooseItem, 429 selectName: $selectName, 430 layer: 2, 431 folderList: $folderList, 432 fileList: $fileList, 433 isClickExpand: $isClickExpand 434 }) 435 } 436 }) 437 } 438 } 439 } 440 .width('100%') 441 .scrollBar(BarState.Off) 442 .layoutWeight(1) 443 .padding({ bottom: $r('app.float.common_padding10') }) 444 .align(Alignment.TopStart) 445 } 446 .width('100%') 447 .height('100%') 448 .backgroundColor($r('app.color.white')) 449 .borderRadius({ topLeft: $r('app.float.common_size24'), topRight: $r('app.float.common_size24') }) 450 } 451} 452