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 { BreadData, FilesData, FileDataSource } from '../../../databases/model/FileData';
17import { TopBar } from '../../component/common/TopBar';
18import { BreadCrumb } from '../../component/myphone/BreadCrumb';
19import { FilesList } from '../../component/myphone/FilesList';
20import { setSystemBar, setImmersion } from '../../../base/utils/Common';
21import router from '@ohos.router';
22import FileAccessExec from '../../../base/utils/FileAccessExec';
23import { SYSTEM_BAR_COLOR } from '../../../base/constants/UiConstant';
24import { getResourceString } from '../../../base/utils/Tools';
25import { TopOperateBar } from '../../component/common/TopOperateBar';
26import { FileMkdirDialog } from '../../component/dialog/FileMkdirDialog';
27import { Loading } from '../../component/common/Loading';
28import { getMediaType, getDurationByUri } from '../../../databases/model/FileAssetModel';
29import Logger from '../../../base/log/Logger';
30import multimedia_image from '@ohos.multimedia.image';
31import AbilityCommonUtil, { ResultCodePicker } from '../../../base/utils/AbilityCommonUtil';
32import ObjectUtil from '../../../base/utils/ObjectUtil';
33import StringUtil from '../../../base/utils/StringUtil';
34import { FileUtil } from '../../../base/utils/FileUtil';
35import fileAccess from '@ohos.file.fileAccess';
36import { StartModeOptions } from '../../../base/model/StartModeOptions';
37import { FilePickerUtil } from '../../../base/utils/FilePickerUtil';
38import { photoAccessHelper } from '@kit.MediaLibraryKit';
39
40const TAG = 'myPhone';
41let storage = LocalStorage.getShared();
42
43@Entry(storage)
44@Component
45struct MyPhone {
46  /**
47   * 正在加载
48   */
49  private startModeOptions: StartModeOptions = FilePickerUtil.getStartOptionsFromStorage();
50  @State isShowLoading: boolean = true;
51  /**
52   * 文件或文件夹数据
53   */
54  @State fileListSource: FileDataSource = new FileDataSource();
55  /**
56   * 面包屑
57   */
58  @State @Watch('onDireListChange') direList: BreadData[] = [];
59  @State selectFilesList: FilesData[] = [];
60  @State isMulti: boolean = false;
61  @State selectAll: boolean = false;
62  @State @Watch('checkedNumChange') checkedNum: number = 0;
63  @Provide isList: boolean = true;
64  @State selectName: string = '';
65  @State fileSize: number = 0;
66  fileMkdirDialog: CustomDialogController = new CustomDialogController({
67    builder: FileMkdirDialog({
68      fileItems: this.fileListSource.getDataArray(),
69      getCurrentDir: this.getCurrentDirUri(),
70      confirm: this.refreshData.bind(this)
71    }),
72    autoCancel: true,
73    alignment: DialogAlignment.Bottom,
74    offset: { dx: 0, dy: -80 }
75  });
76
77  checkedNumChange(): void {
78    this.selectAll = this.checkedNum === this.fileListSource.totalCount();
79    this.selectFilesList = this.fileListSource.getSelectedFileList();
80    this.fileSize = 0;
81    this.selectFilesList.forEach(item => {
82      this.fileSize += item.size;
83    });
84  }
85
86  getCurrentDirUri(): string {
87    if (this.direList.length) {
88      const lastBread = this.direList[this.direList.length-1];
89      return lastBread.url;
90    } else if (globalThis.documentInfo) {
91      return globalThis.documentInfo.uri;
92    } else {
93      Logger.e(TAG, 'currentDir uri is null');
94      return '';
95    }
96  }
97
98  async getBreadCrumb(data: string): Promise<void> {
99    if (!data) {
100      data = '';
101    }
102    if (FileUtil.isUriPath(data)) {
103      let fileHelper = await FileUtil.getFileAccessHelperAsync(globalThis.abilityContext);
104      // 将uri转换成相对路径
105      let curFileInfo: fileAccess.FileInfo = await FileUtil.getFileInfoByUri(data, fileHelper);
106      if (!ObjectUtil.isNullOrUndefined(curFileInfo)) {
107        data = FileUtil.getCurrentDir(curFileInfo.relativePath, FileUtil.isFolder(curFileInfo.mode));
108      }
109    }
110    data = FileUtil.getPathWithFileSplit(data);
111
112    let fileIterator;
113    let fileData;
114    let isContinue: boolean = true;
115    let isRoot: boolean = true;
116    while (isContinue) {
117      isContinue = false;
118      if (!fileIterator) {
119        fileData = FileAccessExec.getRootFolder();
120        isRoot = true;
121      } else {
122        fileData = FileAccessExec.getFileByCurIterator(fileIterator);
123        isRoot = false;
124      }
125      if (Array.isArray(fileData)) {
126        isContinue = true;
127        for (let i = 0; i < fileData.length; i++) {
128          let fileName: string = fileData[i].fileName;
129          let currentDir: string = FileUtil.getPathWithFileSplit(fileData[i].currentDir);
130          if (data.startsWith(currentDir)) {
131            if (fileData[i].isFolder) {
132              this.direList.push({ title: fileName, url: fileData[i].uri, fileIterator: fileData[i].fileIterator });
133              fileIterator = fileData[i].fileIterator;
134              if (data === currentDir) {
135                isContinue = false;
136              } else {
137                break;
138              }
139            }
140          }
141        }
142      }
143      if (isRoot && !fileIterator) {
144        isContinue = false;
145      }
146    }
147    if (fileIterator) {
148      fileData = FileAccessExec.getFileByCurIterator(fileIterator);
149      this.fileListSource.setData(fileData);
150    } else {
151      this.getRootListFile();
152    }
153  }
154
155  onPageShow() {
156    // 文件选择器并且是多选模式下详情返回不更新,避免原有多选被重置
157    if (this.isMulti) {
158      return;
159    }
160    setImmersion(false);
161    setSystemBar(SYSTEM_BAR_COLOR.WHITE, SYSTEM_BAR_COLOR.WHITE, SYSTEM_BAR_COLOR.BLACK, SYSTEM_BAR_COLOR.BLACK);
162  }
163
164  getRootListFile() {
165    let fileList = FileAccessExec.getRootFolder();
166    this.fileListSource.setData(fileList);
167    this.getVideoAudioDuration(fileList);
168  }
169
170  getListFile(fileInfo) {
171    let fileList = FileAccessExec.getFileByCurIterator(fileInfo);
172    this.fileListSource.setData(fileList);
173    this.getVideoAudioDuration(fileList);
174  }
175
176  async getVideoAudioDuration(fileList: FilesData[]) {
177    const videoAudioList = fileList.filter(item => item.mimeTypeObj.isVideo() || item.mimeTypeObj.isAudio());
178    for (let item of videoAudioList) {
179      const mediaType: photoAccessHelper.PhotoType = getMediaType(item.fileName);
180      await getDurationByUri(mediaType, item.uri).then((duration) => {
181        item.duration = duration;
182        let index = this.fileListSource.getIndex(item.uri);
183        if (index >= 0) {
184          this.fileListSource.replaceData(index, item);
185        }
186      })
187    }
188  }
189
190  addCallBack() {
191    this.fileMkdirDialog.open();
192  }
193
194  initData(): void {
195    this.selectFilesList = [];
196    this.selectAll = false;
197    this.isMulti = false;
198    this.checkedNum = 0;
199    // 全部数据列表的isChecked置为false
200    this.fileListSource.selectAll(false);
201  }
202
203  backCallback(): void {
204    if (!this.isMulti) {
205      AbilityCommonUtil.terminateFilePicker([], ResultCodePicker.SUCCESS, this.startModeOptions);
206    } else {
207      this.initData();
208    }
209  }
210
211  menuCallback(): void {
212    this.selectAll = !this.selectAll;
213    this.fileListSource.selectAll(this.selectAll);
214    if (this.selectAll) {
215      this.checkedNum = this.fileListSource.totalCount();
216    } else {
217      this.checkedNum = 0;
218      this.selectFilesList = [];
219    }
220  }
221
222  refreshData() {
223    if (this.direList.length) {
224      const lastBread = this.direList[this.direList.length-1];
225      this.getListFile(lastBread.fileIterator);
226    } else {
227      this.getRootListFile();
228    }
229  }
230
231  aboutToAppear(): void {
232    // 激活image媒体库,能够读取缩略图pixelMap
233    multimedia_image.createPixelMap(new ArrayBuffer(4096), { size: { height: 1, width: 2 } }).then((pixelMap) => {
234    })
235    this.setShowLoading(true);
236    let pickPath = this.getParams(this.startModeOptions);
237    if (StringUtil.isEmpty(pickPath)) {
238      this.getRootListFile();
239    } else {
240      this.getBreadCrumb(pickPath);
241    }
242    this.setShowLoading(false);
243  }
244
245  aboutToDisappear() {
246    this.fileMkdirDialog = null;
247  }
248
249  getParams(startModeOptions: StartModeOptions): string {
250    let defaultPickPath = startModeOptions.defaultFilePathUri;
251    if (!ObjectUtil.isNullOrUndefined(defaultPickPath)) {
252      return defaultPickPath;
253    }
254    let params = router.getParams();
255    if (!ObjectUtil.isNullOrUndefined(params)) {
256      defaultPickPath = params['path'];
257      if (!ObjectUtil.isNullOrUndefined(defaultPickPath)) {
258        return defaultPickPath;
259      }
260    }
261    return '';
262  }
263
264  onDireListChange(): void {
265    if (this.isMulti) {
266      this.backCallback();
267    }
268    this.setShowLoading(true);
269    if (this.direList.length) {
270      const lastBreadCrumb = this.direList[this.direList.length-1];
271      this.getListFile(lastBreadCrumb.fileIterator);
272    } else {
273      this.getRootListFile();
274    }
275    this.setShowLoading(false);
276    Logger.i(TAG, 'onDireListChange BreadCrumb length:' + this.direList.length);
277  }
278
279  setShowLoading(isShow: boolean) {
280    this.isShowLoading = isShow;
281  }
282
283  onBackPress() {
284    const direList = this.direList;
285    const dirLen = this.direList.length;
286    if (this.isMulti) {
287      this.initData();
288      return true;
289    } else {
290      if (router.getParams()) {
291        router.back();
292      } else {
293        if (dirLen) {
294          direList.splice(-1, 1);
295          if (direList.length) {
296            const lastDir = direList[direList.length-1];
297            this.getListFile(lastDir.fileIterator);
298          } else {
299            this.getRootListFile();
300          }
301        } else {
302          AbilityCommonUtil.terminateFilePicker([], ResultCodePicker.CANCEL, this.startModeOptions);
303        }
304      }
305      return true;
306    }
307  }
308
309  build() {
310    if (this.startModeOptions.isUxt()) {
311      Column() {
312      }.bindSheet(true, this.mainContent(), {
313        height: '95%',
314        dragBar: false,
315        showClose: false,
316        preferType: SheetType.CENTER,
317        onAppear: () => {
318        },
319        shouldDismiss: () => {
320          this.startModeOptions.session.terminateSelf();
321        }
322      })
323    } else {
324      this.mainContent()
325    }
326  }
327
328  @Builder
329  mainContent() {
330    Column() {
331      // 头部导航
332      TopBar({
333        startModeOptions: this.startModeOptions,
334        title: getResourceString($r('app.string.myPhone')),
335        isMulti: this.isMulti,
336        selectAll: this.selectAll,
337        checkedNum: $checkedNum,
338        checkedList: $selectFilesList,
339        backCallback: this.backCallback.bind(this),
340        menuCallback: this.menuCallback.bind(this)
341      })
342      // 面包屑
343      BreadCrumb({
344        direList: $direList
345      })
346
347      TopOperateBar({
348        isDisabled: this.isMulti,
349        addFolder: this.addCallBack.bind(this)
350      })
351
352      Column() {
353        Loading({ isLoading: !!this.isShowLoading })
354        if (!this.isShowLoading) {
355          // 文件列表
356          FilesList({
357            startModeOptions: this.startModeOptions,
358            fileListSource: $fileListSource,
359            direList: $direList,
360            isMulti: $isMulti,
361            checkedNum: $checkedNum
362          })
363        }
364      }.layoutWeight(1)
365    }
366    .width('100%')
367  }
368}