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