1/* 2 * Copyright (c) 2022-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 { MathUtil } from './MathUtil'; 17import { Constants } from '../model/common/Constants'; 18import { Log } from './Log'; 19import { ScreenManager } from '../model/common/ScreenManager'; 20import prompt from '@system.prompt'; 21import type window from '@ohos.window'; 22import type { Action } from '../view/browserOperation/Action'; 23import { AlbumInfo } from '../model/browser/album/AlbumInfo'; 24import common from '@ohos.app.ability.common'; 25import resourceManager from '@ohos.resourceManager'; 26import { UserFileManagerAccess } from '../access/UserFileManagerAccess'; 27import { MediaItem } from '../model/browser/photo/MediaItem'; 28 29const TAG: string = 'common_UiUtil'; 30 31type Window = window.Window; 32 33export class UiUtil { 34 35 /** 36 * Status bar height 37 */ 38 static readonly STATUS_BAR_HEIGHT = 45; 39 40 /** 41 * Navigation bar height 42 */ 43 static readonly NAVI_BAR_HEIGHT = 45; 44 45 /** 46 * Hexadecimal Radix 47 */ 48 static readonly HEX_BASE = 16; 49 50 /** 51 * Maximum order of color 52 */ 53 static readonly MAX_COLOR_ORDER = 255; 54 public static readonly TOAST_DURATION = 3000; 55 /** 56 * 3-bit length hex color 57 */ 58 private static readonly HEX_COLOR_LENGTH_THREE = 3; 59 /** 60 * 8-bit length hex color 61 */ 62 private static readonly HEX_COLOR_LENGTH_EIGHT = 8; 63 /** 64 * Opacity length of hex color 65 */ 66 private static readonly HEX_COLOR_OPACITY_LENGTH = 2; 67 /** 68 * Hexadecimal array 69 */ 70 private static readonly HEX_ARRAY = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']; 71 72 /** 73 * Set status bar color 74 * 75 * @param statusBarColor statusBarColor 76 */ 77 static setStatusBarColor(topWindow: Window): void { 78 topWindow.setSystemBarProperties({ navigationBarColor: '#FFF1F3F5', navigationBarContentColor: '#FF000000' }, 79 () => { 80 Log.info(TAG, 'setStatusBarColor done'); 81 }); 82 } 83 84 /** 85 * Gets the hexadecimal color with opacity 86 * 87 * @param color Original hex color 88 * @param opacity Opacity 89 * @returns Hex color with opacity 90 */ 91 static getHexOpacityColor(paramColor: string, paramOpacity: number): string { 92 let colorReg = /^\#([0-9a-fA-f]{3}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/; 93 // The input must be # the first 3-bit / 6-bit / 8-bit hex color 94 if (paramColor.match(colorReg) == null) { 95 Log.error(TAG, `getHexOpacityColor failed because of invalid input, color: ${paramColor}`); 96 return paramColor; 97 } 98 let color = paramColor.replace(/\#/g, '').toUpperCase(); 99 let opacity = MathUtil.clamp(0, 1, paramOpacity); 100 // If it is an abbreviated 3-digit color, complete the 3-digit characters to 6-digit characters 101 if (color.length === UiUtil.HEX_COLOR_LENGTH_THREE) { 102 let arr = color.split(''); 103 color = ''; 104 for (let i = 0; i < arr.length; i++) { 105 color += (arr[i] + arr[i]); 106 } 107 } 108 // If it is an 8-bit color, the original opacity will be removed 109 if (color.length === UiUtil.HEX_COLOR_LENGTH_EIGHT) { 110 color = color.slice(UiUtil.HEX_COLOR_OPACITY_LENGTH, color.length); 111 } 112 let opacityNum = Math.round(UiUtil.MAX_COLOR_ORDER * opacity); // rounding 113 let opacityStr = ''; 114 while (opacityNum > 0) { 115 let mod = opacityNum % UiUtil.HEX_BASE; 116 opacityNum = (opacityNum - mod) / UiUtil.HEX_BASE; 117 opacityStr = UiUtil.HEX_ARRAY[mod] + opacityStr; 118 } 119 if (opacityStr.length == 1) { 120 opacityStr = `0${opacityStr}`; 121 } 122 if (opacityStr.length == 0) { 123 opacityStr = '00'; 124 } 125 return `#${opacityStr + color}`; 126 } 127 128 /** 129 * Get the content of the resource reference 130 * 131 * @param resource resource reference 132 * @returns resource Corresponding content string 133 */ 134 static async getResourceString(resource: Resource): Promise<string> { 135 try { 136 Log.info(TAG, `getResourceString: ${JSON.stringify(resource)}`); 137 if (AppStorage.get('photosAbilityContext') === null || AppStorage.get('photosAbilityContext') === 'undefined') { 138 Log.error(TAG, 'getResourceString error: context is null'); 139 return null; 140 } 141 let context: common.UIAbilityContext = AppStorage.get<common.UIAbilityContext>('photosAbilityContext'); 142 let mgr: string = await context.resourceManager.getString(resource.id); 143 if (mgr) { 144 return mgr; 145 } else { 146 Log.error(TAG, `getResourceManager instance is none`); 147 return null; 148 } 149 } catch (error) { 150 Log.error(TAG, `getResourceString error: ${error}`); 151 return null; 152 } 153 } 154 155 private static getResourceManager(src: string = 'photosAbilityContext'): resourceManager.ResourceManager | undefined { 156 const resourceManager: resourceManager.ResourceManager | undefined = 157 AppStorage.get<common.UIAbilityContext>(src)?.resourceManager; 158 if (resourceManager === undefined) { 159 Log.error(TAG, JSON.stringify({ context: AppStorage.get<common.UIAbilityContext>(src), resourceManager })); 160 } 161 return resourceManager; 162 } 163 164 static async getResourceNumber(resource: Resource): Promise<number> { 165 try { 166 Log.info(TAG, `getResourceNumber: ${JSON.stringify(resource)}`); 167 if (AppStorage.get('photosAbilityContext') === null || AppStorage.get('photosAbilityContext') === 'undefined') { 168 Log.error(TAG, 'getResourceNumber error: context is null'); 169 return null; 170 } 171 let context: common.UIAbilityContext = AppStorage.get<common.UIAbilityContext>('photosAbilityContext'); 172 let mgr: number = context.resourceManager.getNumber(resource.id); 173 if (mgr) { 174 return mgr; 175 } else { 176 Log.error(TAG, `getResourceNumber instance is none`); 177 return null; 178 } 179 } catch (error) { 180 Log.error(TAG, `getResourceNumber error: ${error}`); 181 return null; 182 } 183 } 184 185 /** 186 * 用于检查该文件是否可以编辑,用于控制编辑菜单是否灰化 187 * 188 * @param mediaItem 被检测的媒体 189 * 190 * @returns true 可以编辑,false 不可以编辑 191 */ 192 static isEditedEnable(mediaItem: MediaItem): boolean { 193 if (mediaItem.mediaType !== UserFileManagerAccess.MEDIA_TYPE_IMAGE && mediaItem.mediaType !== UserFileManagerAccess.MEDIA_TYPE_VIDEO) { 194 Log.warn(TAG, 'the mediaType is invalid, the type is ' + mediaItem.mediaType); 195 return false; 196 } 197 if (mediaItem.size === undefined || mediaItem.size === 0) { 198 Log.warn(TAG, 'the size is invalid, the size is ' + mediaItem.size); 199 return false; 200 } 201 if (mediaItem.width === undefined || mediaItem.width === 0) { 202 Log.warn(TAG, 'the width is invalid, the width is ' + mediaItem.width); 203 return false; 204 } 205 if (mediaItem.height === undefined || mediaItem.height === 0) { 206 Log.warn(TAG, 'the height is invalid, the height is ' + mediaItem.height); 207 return false; 208 } 209 if (mediaItem.mediaType === UserFileManagerAccess.MEDIA_TYPE_VIDEO && (mediaItem.duration === undefined || mediaItem.duration === 0)) { 210 Log.warn(TAG, 'the duration is invalid, the duration is ' + mediaItem.duration); 211 return false; 212 } 213 return true; 214 } 215 216 /** 217 * Get the content of the resource reference 218 * 219 * @param resource resource reference 220 * @returns resource Corresponding content number 221 */ 222 static getResourceNumberSync(resource: Resource): number { 223 Log.info(TAG, `getResourceNumber: ${JSON.stringify(resource)}`); 224 return this.getResourceManager()?.getNumber(resource.id) ?? Number.NaN; 225 } 226 227 /** 228 * Get the content of the resource reference 229 * 230 * @param resource resource reference 231 * @returns resource Corresponding content Color 232 */ 233 static getResourceColorSync(resource: Resource): number { 234 try { 235 Log.info(TAG, `getResourceString: ${JSON.stringify(resource)}`); 236 if (globalThis.photosAbilityContext == null || globalThis.photosAbilityContext === 'undefined') { 237 Log.error(TAG, 'getResourceString error: context is null'); 238 return null; 239 } 240 let mgr = globalThis.photosAbilityContext.resourceManager.getColorSync(resource.id); 241 if (mgr) { 242 return mgr; 243 } else { 244 Log.error(TAG, `getResourceManager instance is none`); 245 return null; 246 } 247 } catch (error) { 248 Log.error(TAG, `getResourceString error: ${error}`); 249 return null; 250 } 251 } 252 253 static async showToast(resource: Resource): Promise<void> { 254 let message = await UiUtil.getResourceString(resource); 255 Log.debug(TAG, `showToast: ${message}`); 256 prompt.showToast({ 257 message: message, 258 duration: UiUtil.TOAST_DURATION, 259 bottom: '64vp' 260 }); 261 } 262 263 /** 264 * Get the content of the resource reference 265 * 266 * @param resource resource reference 267 * @returns resource Corresponding content string 268 */ 269 static getResourceStringSync(resource: Resource): string { 270 Log.info(TAG, `getResourceString: ${JSON.stringify(resource)}`); 271 if (resource.moduleName === 'browserlibrary') { 272 return this.getResourceManager('photoLibraryContext')?.getStringSync(resource.id) ?? ''; 273 } 274 return this.getResourceManager()?.getStringSync(resource.id) ?? ''; 275 } 276 277 /** 278 * 获取相册封面宫格布局列的个数 279 * 280 * @param isSidebar 是否包含侧边栏 281 */ 282 static getAlbumGridCount(isSidebar: boolean): number { 283 Log.info(TAG, `get screen width is : ${ScreenManager.getInstance().getWinWidth()}`); 284 let sideBarWidth = isSidebar ? Constants.TAB_BAR_WIDTH : 0; 285 let contentWidth = ScreenManager.getInstance().getWinWidth() - sideBarWidth; 286 let maxCardWidth = Constants.ALBUM_SET_COVER_SIZE * Constants.GRID_MAX_SIZE_RATIO; 287 let gridColumnsCount = Math.max(Constants.DEFAULT_ALBUM_GRID_COLUMN_MIN_COUNT, 288 Math.ceil((contentWidth - Constants.ALBUM_SET_MARGIN * Constants.NUMBER_2 + Constants.ALBUM_SET_GUTTER) / 289 (maxCardWidth + Constants.ALBUM_SET_GUTTER))); 290 Log.info(TAG, `[getAlbumGridCount] contentWidth: ${contentWidth}, gridColumnsCount: ${gridColumnsCount}`); 291 return gridColumnsCount; 292 } 293 294 static resetGeometryTransitionParams(): void { 295 AppStorage.setOrCreate<number>('geometryScale', 1); 296 AppStorage.setOrCreate<number>('geometryOffsetX', 0); 297 AppStorage.setOrCreate<number>('geometryOffsetY', 0); 298 AppStorage.setOrCreate<number>('geometryOpacity', 1); 299 } 300 301 static updateGeometryTapInfo(geometryTapIndex: number, geometryTransitionString: string): void { 302 AppStorage.setOrCreate<number>('placeholderIndex', geometryTapIndex); 303 AppStorage.setOrCreate<string>('geometryTransitionBrowserId', 304 geometryTransitionString); 305 Log.debug(TAG, 'this.geometryTransitionId = ' + geometryTransitionString + 306 ', placeholderIndex = ' + geometryTapIndex); 307 } 308 309 static getRouterParams(params: Object): Object { 310 let fakeRouterParam: Object = AppStorage.Get('fakeRouterParam'); 311 AppStorage.delete('fakeRouterParam'); 312 if (fakeRouterParam) { 313 Log.debug(TAG, 'fakeRouterParam = ' + JSON.stringify(fakeRouterParam)); 314 } 315 if (params) { 316 Log.debug(TAG, 'params = ' + JSON.stringify(params)); 317 } 318 return params ? params : fakeRouterParam; 319 } 320 321 static isActionArrayEqual(firstList: Array<Action>, secondList: Array<Action>): boolean { 322 if (firstList === secondList) { 323 return true; 324 } 325 if (firstList.length !== secondList.length) { 326 return false; 327 } 328 for (let index = 0; index < firstList.length; index++) { 329 if (!firstList[index].equals(secondList[index])) { 330 return false; 331 } 332 } 333 return true; 334 } 335 336 static getDisplayNameResourceByAlbumInfo(albumInfo: AlbumInfo): Resource { 337 let res: Resource = null; 338 if (albumInfo.isPhotoAlbum) { 339 return $r('app.string.album_photos'); 340 } else if (albumInfo.isFavorAlbum) { 341 return $r('app.string.album_favor'); 342 } else if (albumInfo.isVideoAlbum) { 343 return $r('app.string.album_video'); 344 } else if (albumInfo.isTrashAlbum) { 345 return $r('app.string.album_recycle'); 346 } else if (albumInfo.isScreenShotAlbum) { 347 return $r('app.string.album_screen_shot'); 348 } else { 349 return res; 350 } 351 } 352}