• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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