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