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}