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