1/** 2 * Copyright (c) 2021-2022 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 { Log } from '../utils/Log'; 17import { SettingsModel } from '../model/SettingsModel'; 18import { StyleConstants } from '../constants/StyleConstants'; 19import { CommonConstants } from '../constants/CommonConstants'; 20import { ResourceManager } from '../manager/ResourceManager'; 21import { PresetStyleConstants } from '../constants/PresetStyleConstants'; 22import { AppIcon } from './AppIcon'; 23import { AppName } from './AppName'; 24import { AppMenu } from './AppMenu'; 25import { LauncherDragItemInfo } from '../bean/LauncherDragItemInfo'; 26import { AppItemInfo } from '../bean/AppItemInfo'; 27import { FolderItemInfo } from '../bean/FolderItemInfo'; 28import { MenuInfo } from '../bean'; 29import { FolderData } from '../interface/FolderData'; 30 31const TAG = 'FolderComponent'; 32 33interface FolderAnimateData { 34 folderId?: string; 35 isOpenFolder?: boolean; 36} 37 38class FolderItem { 39 layoutInfo: AppItemInfo[][] = []; 40 folderId: string = ''; 41 folderName: string = ''; 42} 43 44class SuperposeApp extends AppItemInfo { 45 isEmpty?: boolean; 46 alignContent?: Alignment; 47} 48 49@Component 50export struct FolderComponent { 51 @StorageLink('openFolderStatus') @Watch('updateFolderAnimate') openFolderStatus: number = 1; 52 @State folderAnimateData: FolderAnimateData = { folderId: '', isOpenFolder: false }; 53 @State folderPositionX: number = 0; 54 @State folderPositionY: number = 0; 55 @State folderItemPositionX: number = 0; 56 @State folderItemPositionY: number = 0; 57 @State animateFolderPositionX: number = 0; 58 @State animateFolderPositionY: number = 0; 59 @State animateOpacity: number = 1.0; 60 @State animateScale: number = 1.0; 61 @State showFolderName: boolean = true; 62 @State folderNameHeight: number = 0; 63 @State folderNameSize: number = 0; 64 @State nameFontColor: string = ''; 65 @State appIconSize: number = 0; 66 @State superposeIconVisible: boolean = false; 67 @State isHover: boolean = false; 68 mPaddingTop: number = StyleConstants.DEFAULT_10; 69 folderGridSize: number = StyleConstants.DEFAULT_FOLDER_GRID_SIZE; 70 gridMargin: number = StyleConstants.DEFAULT_FOLDER_GRID_MARGIN; 71 gridGap: number = StyleConstants.DEFAULT_FOLDER_GRID_GAP; 72 badgeNumber: number = 0; 73 private mSettingsModel: SettingsModel = SettingsModel.getInstance(); 74 private isPad: boolean = false; 75 private mFolderItem: LauncherDragItemInfo = new LauncherDragItemInfo(); 76 private mShowAppList: AppItemInfo[] = []; 77 private mSuperposeAppList: SuperposeApp[] = []; 78 onAppIconClick: Function = (event: ClickEvent, item: AppItemInfo) => {}; 79 onOpenFolderClick: Function = (event: ClickEvent, folderItem: FolderData) => {}; 80 onFolderTouch: Function = (event: ClickEvent, folderItem: FolderData) => {}; 81 onGetPosition: Function = (callback: (x: number, y: number) => void) => {}; 82 buildMenu: (item: LauncherDragItemInfo) => MenuInfo[] = (item: LauncherDragItemInfo) => []; 83 folderNameLines: number = PresetStyleConstants.DEFAULT_APP_NAME_LINES; 84 iconNameMargin: number = PresetStyleConstants.DEFAULT_ICON_NAME_GAP; 85 isSelect: boolean = false; 86 dragStart: Function = (event: DragEvent) => {}; 87 88 aboutToAppear(): void { 89 Log.showInfo(TAG, `aboutToAppear start`); 90 this.updateShowList(); 91 this.mSettingsModel = SettingsModel.getInstance(); 92 if (this.mSettingsModel.getDevice() != "phone") { 93 this.isPad = true; 94 } 95 } 96 97 aboutToDisappear(): void { 98 } 99 100 updateShowList(): void { 101 if (typeof this.mFolderItem.layoutInfo === 'undefined') return; 102 if (this.mFolderItem.layoutInfo[0].length > CommonConstants.FOLDER_STATIC_SHOW_LENGTH) { 103 this.mShowAppList = this.mFolderItem.layoutInfo[0].slice(0, CommonConstants.FOLDER_STATIC_SHOW_LENGTH); 104 } else { 105 this.mShowAppList = this.mFolderItem.layoutInfo[0]; 106 } 107 108 let showLength = CommonConstants.FOLDER_STATIC_SHOW_LENGTH - CommonConstants.FOLDER_STATIC_SUPERPOSEAPP_LENGTH; 109 if (this.mShowAppList.length > showLength) { 110 this.mSuperposeAppList = this.mShowAppList.slice(showLength); 111 this.mShowAppList = this.mShowAppList.slice(0, showLength); 112 this.superposeIconVisible = true; 113 } 114 115 let length = this.mSuperposeAppList.length; 116 let mSuperposeApp = new SuperposeApp(); 117 if (length > CommonConstants.FOLDER_STATIC_SUPERPOSEAPP_LENGTH) { 118 this.mSuperposeAppList = this.mSuperposeAppList.slice(0, CommonConstants.FOLDER_STATIC_SUPERPOSEAPP_LENGTH); 119 } else { 120 for (let i = 0; i < (CommonConstants.FOLDER_STATIC_SUPERPOSEAPP_LENGTH - length); i++) { 121 mSuperposeApp.isEmpty = true; 122 this.mSuperposeAppList.push(mSuperposeApp); 123 } 124 } 125 this.mSuperposeAppList = this.mSuperposeAppList.reverse(); 126 this.mSuperposeAppList[0].alignContent = Alignment.TopStart; 127 this.mSuperposeAppList[1].alignContent = Alignment.Center; 128 this.mSuperposeAppList[2].alignContent = Alignment.BottomEnd; 129 130 Log.showInfo(TAG, `superposeIconVisible:${this.superposeIconVisible}`); 131 Log.showInfo(TAG, `FolderItem.layoutInfo[0].length:${this.mFolderItem.layoutInfo[0].length}`); 132 Log.showInfo(TAG, `mSuperposeAppList length:${this.mSuperposeAppList.length}`); 133 } 134 135 @Builder MenuBuilder() { 136 Column() { 137 AppMenu({ 138 menuInfoList: this.buildMenu(this.mFolderItem), 139 }) 140 } 141 .alignItems(HorizontalAlign.Center) 142 .justifyContent(FlexAlign.Center) 143 .width(StyleConstants.CONTEXT_MENU_WIDTH) 144 } 145 146 private updateFolderAnimate() { 147 Log.showInfo(TAG, `updateFolderAnimate start`); 148 if (this.openFolderStatus == 0) { 149 this.folderAnimateData = AppStorage.get('folderAnimateData') as FolderAnimateData; 150 if (this.mFolderItem.folderId === this.folderAnimateData.folderId && 151 this.folderAnimateData.isOpenFolder && 152 this.folderAnimateData.folderId != '' && 153 this.animateOpacity != 1.0 && 154 this.animateScale != 1.0) { 155 this.folderAnimateData.isOpenFolder = false; 156 AppStorage.setOrCreate('folderAnimateData', this.folderAnimateData); 157 Log.showInfo(TAG, `updateFolderAnimate show`); 158 this.showAnimate(1.0, 1.0, false); 159 } 160 } 161 } 162 163 private showAnimate(animateScale: number, animateOpacity: number, isMoveFolder: boolean) { 164 let positionX = 0; 165 let positionY = 0; 166 if (this.onGetPosition) { 167 this.onGetPosition(this.getPosition); 168 if (isMoveFolder) { 169 positionX = this.animateFolderPositionX; 170 positionY = this.animateFolderPositionY; 171 } 172 } 173 animateTo({ 174 duration: 250, 175 tempo: 0.5, 176 curve: Curve.Friction, 177 delay: 0, 178 iterations: 1, 179 playMode: PlayMode.Normal, 180 onFinish: () => { 181 Log.showInfo(TAG, ` onFinish x: ${this.folderPositionX}, y: ${this.folderPositionY}`); 182 } 183 }, () => { 184 this.animateScale = animateScale; 185 this.animateOpacity = animateOpacity; 186 this.folderPositionX = positionX; 187 this.folderPositionY = positionY; 188 }) 189 } 190 191 public getPosition = (x: number, y: number): void => { 192 this.folderItemPositionX = x; 193 this.folderItemPositionY = y; 194 let screenWidth: number = AppStorage.get('screenWidth') as number; 195 let screenHeight: number = AppStorage.get('screenHeight') as number; 196 this.animateFolderPositionX = (screenWidth - this.folderGridSize * 1.5) / 2 - this.folderItemPositionX; 197 this.animateFolderPositionY = (screenHeight - this.folderGridSize * 1.5) / 2 - this.folderItemPositionY; 198 Log.showInfo(TAG, `getPosition animatePosition x: ${this.animateFolderPositionX}, y: ${this.animateFolderPositionY}`); 199 } 200 201 build() { 202 Column() { 203 Column() { 204 Badge({ 205 count: this.badgeNumber, 206 maxCount: StyleConstants.MAX_BADGE_COUNT, 207 style: { 208 color: StyleConstants.DEFAULT_FONT_COLOR, 209 fontSize: StyleConstants.DEFAULT_BADGE_FONT_SIZE, 210 badgeSize: (this.badgeNumber > 0 ? StyleConstants.DEFAULT_BADGE_SIZE : 0), 211 badgeColor: Color.Red, 212 } 213 }) { 214 Stack() { 215 Column() { 216 } 217 .backgroundColor(Color.White) 218 .borderRadius(24) 219 .opacity(0.5) 220 .height(this.folderGridSize) 221 .width(this.folderGridSize) 222 223 Grid() { 224 ForEach(this.mShowAppList, (item: AppItemInfo) => { 225 GridItem() { 226 AppIcon({ 227 iconSize: this.appIconSize, 228 iconId: item.appIconId, 229 icon: ResourceManager.getInstance().getCachedAppIcon(item.appIconId, item.bundleName, item.moduleName), 230 bundleName: item.bundleName, 231 moduleName: item.moduleName, 232 badgeNumber: item.badgeNumber 233 }) 234 } 235 .height(StyleConstants.PERCENTAGE_100) 236 .width(StyleConstants.PERCENTAGE_100) 237 .onClick((event: ClickEvent) => { 238 if (this.onAppIconClick) { 239 this.onAppIconClick(event, item); 240 } 241 }) 242 }, (item: AppItemInfo) => JSON.stringify(item)) 243 244 if (this.mSuperposeAppList.length > 0) { 245 GridItem() { 246 Stack() { 247 ForEach(this.mSuperposeAppList, (item: SuperposeApp) => { 248 Stack({ alignContent: item.alignContent }) { 249 if (item.isEmpty) { 250 Column() { 251 Column() { 252 } 253 .backgroundColor(Color.White) 254 .borderRadius(10) 255 .opacity(0.5) 256 .width(StyleConstants.PERCENTAGE_100) 257 .height(StyleConstants.PERCENTAGE_100) 258 } 259 .alignItems(HorizontalAlign.Start) 260 .width(StyleConstants.PERCENTAGE_80) 261 .height(StyleConstants.PERCENTAGE_80) 262 } else { 263 Column() { 264 AppIcon({ 265 iconSize: this.appIconSize * StyleConstants.PERCENTAGE_80_number, 266 iconId: item.appIconId, 267 icon: ResourceManager.getInstance().getCachedAppIcon(item.appIconId, item.bundleName, item.moduleName), 268 bundleName: item.bundleName, 269 moduleName: item.moduleName, 270 badgeNumber: item.badgeNumber 271 }) 272 } 273 .width(StyleConstants.PERCENTAGE_80) 274 .height(StyleConstants.PERCENTAGE_80) 275 .alignItems(HorizontalAlign.Start) 276 } 277 } 278 .width(StyleConstants.PERCENTAGE_100) 279 .height(StyleConstants.PERCENTAGE_100) 280 }, (item: SuperposeApp) => JSON.stringify(item)) 281 } 282 .width(this.isPad ? 283 StyleConstants.DEFAULT_FOLDER_APP_ITEM_WIDTH_SMALL : 284 StyleConstants.DEFAULT_FOLDER_APP_ITEM_WIDTH) 285 .height(this.isPad ? 286 StyleConstants.DEFAULT_FOLDER_APP_ITEM_WIDTH_SMALL : 287 StyleConstants.DEFAULT_FOLDER_APP_ITEM_WIDTH) 288 } 289 .visibility(this.superposeIconVisible ? Visibility.Visible : Visibility.Hidden) 290 .width(StyleConstants.PERCENTAGE_100) 291 .height(StyleConstants.PERCENTAGE_100) 292 .onClick((event: ClickEvent) => { 293 Log.showInfo(TAG, `last item onClick`); 294 this.showAnimate(1.5, 0, true); 295 if (this.onOpenFolderClick) { 296 this.folderAnimateData.folderId = this.mFolderItem.folderId; 297 this.folderAnimateData.isOpenFolder = true; 298 AppStorage.setOrCreate('folderAnimateData', this.folderAnimateData); 299 this.onOpenFolderClick(event, this.mFolderItem); 300 } 301 }) 302 } 303 } 304 .padding(this.gridMargin) 305 .columnsTemplate('1fr 1fr 1fr') 306 .rowsTemplate('1fr 1fr 1fr') 307 .columnsGap(this.gridGap) 308 .rowsGap(this.gridGap) 309 .onClick((event: ClickEvent) => { 310 Log.showInfo(TAG, `grid onClick`); 311 this.showAnimate(1.5, 0, true); 312 if (this.onOpenFolderClick) { 313 this.folderAnimateData.folderId = this.mFolderItem.folderId; 314 this.folderAnimateData.isOpenFolder = true; 315 AppStorage.setOrCreate('folderAnimateData', this.folderAnimateData); 316 this.onOpenFolderClick(event, this.mFolderItem); 317 } 318 }) 319 .onTouch((event: TouchEvent) => { 320 Log.showInfo(TAG, "onTouch start"); 321 if (this.onFolderTouch) { 322 this.onFolderTouch(event, this.mFolderItem); 323 } 324 Log.showInfo(TAG, "onTouch end"); 325 }) 326 } 327 .height(StyleConstants.PERCENTAGE_100) 328 .width(StyleConstants.PERCENTAGE_100) 329 .onHover((isHover: boolean) => { 330 Log.showInfo(TAG, `onHover isHover:${isHover}`); 331 this.isHover = isHover; 332 }) 333 .bindContextMenu(this.MenuBuilder, ResponseType.LongPress) 334 .onDragStart((event: DragEvent) => { 335 return this.dragStart(event); 336 }) 337 .onDragEnd((event: DragEvent, extraParams: string) => { 338 Log.showInfo(TAG, `onDragEnd event: [${event.getWindowX()}, ${event.getWindowY()}]` + event.getResult()); 339 AppStorage.setOrCreate<LauncherDragItemInfo>('dragItemInfo', new LauncherDragItemInfo()); 340 }) 341 } 342 .height(this.folderGridSize) 343 .width(this.folderGridSize) 344 345 Column() { 346 AppName({ 347 nameHeight: this.folderNameHeight, 348 nameSize: this.folderNameSize, 349 nameFontColor: this.nameFontColor, 350 appName: this.mFolderItem.folderName, 351 nameLines: this.folderNameLines, 352 marginTop: this.iconNameMargin 353 }) 354 } 355 .visibility(this.showFolderName ? Visibility.Visible : Visibility.Hidden) 356 } 357 .bindContextMenu(this.MenuBuilder, ResponseType.RightClick) 358 .width(StyleConstants.PERCENTAGE_100) 359 .height(StyleConstants.PERCENTAGE_100) 360 .offset({ x: this.folderPositionX, y: this.folderPositionY }) 361 .scale({ x: this.isHover ? 1.05 : this.animateScale, y: this.isHover ? 1.05 : this.animateScale }) 362 .opacity(this.animateOpacity) 363 } 364 .width(this.isSelect ? this.folderGridSize + StyleConstants.DEFAULT_40 : StyleConstants.PERCENTAGE_100) 365 .height(this.isSelect ? this.folderGridSize + StyleConstants.DEFAULT_40 : StyleConstants.PERCENTAGE_100) 366 .backgroundColor(this.isSelect ? StyleConstants.DEFAULT_BROAD_COLOR : StyleConstants.DEFAULT_TRANSPARENT_COLOR) 367 .borderRadius(this.isSelect ? StyleConstants.DEFAULT_15 : StyleConstants.DEFAULT_0) 368 .padding(this.isSelect ? { left: StyleConstants.DEFAULT_20, 369 right: StyleConstants.DEFAULT_20, top: this.mPaddingTop + StyleConstants.DEFAULT_10 } : { top: this.mPaddingTop }) 370 } 371}