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