• 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, 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