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 {
17  AppMenu,
18  AppIcon,
19} from '@ohos/common/component'
20import {
21  DockItemInfo,
22  LauncherDragItemInfo,
23  CommonConstants,
24  StyleConstants,
25  ResourceManager,
26  Log,
27  MenuInfo,
28  BadgeManager
29} from '@ohos/common';
30import { SmartDockStyleConfig } from '../config/SmartDockStyleConfig';
31import SmartDockDragHandler from '../common/SmartDockDragHandler';
32import {
33  PageDesktopViewModel
34} from '../../../../../../pagedesktop/src/main/ets/default/viewmodel/PageDesktopViewModel';
35
36let mSmartDockStyleConfig: SmartDockStyleConfig | null = null;
37const TAG = 'ResidentLayout';
38
39interface DockPadding {
40  right: number;
41  left: number;
42  top: number;
43  bottom: number;
44}
45
46@Component
47export default struct ResidentLayout {
48  @StorageLink('dockPadding') dockPadding: DockPadding = { right: 0, left: 0, top: 0, bottom: 0 };
49  @StorageLink('residentList') @Watch('onDockListChange') appList: Array<DockItemInfo> = [];
50  @Link mSmartDockStyleConfig: SmartDockStyleConfig;
51  onItemClick: (event: ClickEvent, item: DockItemInfo) => void = (event: ClickEvent, item: DockItemInfo) => {};
52  buildMenu: (item: DockItemInfo) => MenuInfo[] = (item: DockItemInfo): MenuInfo[] => [];
53  onDockListChangeFunc: () => void = () => {};
54  oldPageList:number[] = [0];
55
56  aboutToAppear(): void {
57    mSmartDockStyleConfig = this.mSmartDockStyleConfig;
58    this.onDockListChange();
59  }
60
61  aboutToDisappear(): void {
62  }
63
64  getListWidth(): number {
65    let residentMaxNum = this.mSmartDockStyleConfig.mMaxDockNum;
66    let width = 0.0;
67    if (AppStorage.get('deviceType') == CommonConstants.DEFAULT_DEVICE_TYPE) {
68      Log.showDebug(TAG, `getListWidth mDockPadding: ${this.mSmartDockStyleConfig.mDockPadding}, mMaxNum: ${residentMaxNum}`);
69      width = this.mSmartDockStyleConfig.mDockPadding * 2 + residentMaxNum *
70        (this.mSmartDockStyleConfig.mListItemWidth) + (residentMaxNum - 1) * (this.mSmartDockStyleConfig.mListItemGap);
71      Log.showDebug(TAG, `getListWidth width: ${width}`);
72    } else {
73      if (this.appList == null || this.appList.length === 0) {
74      } else {
75        let num = this.appList.length;
76        if (num > residentMaxNum) {
77          num = residentMaxNum;
78        }
79        width = this.mSmartDockStyleConfig.mDockPadding * 2 + num *
80          (this.mSmartDockStyleConfig.mListItemWidth) + (num - 1) * (this.mSmartDockStyleConfig.mListItemGap);
81      }
82    }
83    return width;
84  }
85
86  private onDockListChange() {
87    this.onDockListChangeFunc();
88  }
89
90  build() {
91    if (this.getListWidth && this.getListWidth() !== 0) {
92      Row() {
93        List({ space: this.appList.length == 0 ? 0 : this.mSmartDockStyleConfig.mListItemGap }) {
94          ForEach(this.appList, (item: DockItemInfo) => {
95            ListItem() {
96              AppItem({
97                appInfo: item,
98                buildMenu: this.buildMenu,
99                onItemClick: this.onItemClick
100              })
101            }
102          }, (item: DockItemInfo) => JSON.stringify(item))
103        }
104        .enableScrollInteraction(false)
105        .scrollBar(BarState.Off)
106        .height('100%')
107        .animation({
108          curve: Curve.Friction
109        })
110        .listDirection(Axis[this.mSmartDockStyleConfig.mListDirection])
111      }
112      .backgroundColor(this.mSmartDockStyleConfig.mBackgroundColor)
113      .borderRadius(this.mSmartDockStyleConfig.mDockRadius)
114      .backdropBlur(this.mSmartDockStyleConfig.mBackdropBlur)
115      .padding(this.appList.length == 0 ? this.mSmartDockStyleConfig.mDockPadding : this.dockPadding)
116      .width(this.getListWidth())
117      .height(this.mSmartDockStyleConfig.mDockHeight)
118      .justifyContent(FlexAlign.Center)
119      .onDragEnter((event: DragEvent, extraParams: string) => {
120        Log.showDebug(TAG, `onDragEnter extraParams: ${extraParams}, event: [${event.getWindowX()}, ${event.getWindowY()}]`);
121      })
122      .onDragMove((event: DragEvent, extraParams: string) => {
123        Log.showDebug(TAG, `onDragMove event: [${event.getWindowX()}, ${event.getWindowY()}]`);
124      })
125      .onDragLeave((event: DragEvent, extraParams: string) => {
126        Log.showDebug(TAG, `onDragLeave event: [${event.getWindowX()}, ${event.getWindowY()}]`);
127        let oldPageList: number[] | undefined = AppStorage.get('dockAddedPage');
128        if (oldPageList) {
129          this.oldPageList = [...oldPageList];
130        }
131      })
132      .onDrop((event: DragEvent, extraParams: string) => {
133        Log.showInfo(TAG, `onDrop event: [${event.getWindowX()}, ${event.getWindowY()}]`);
134        let newPageList: number[] | undefined = AppStorage.get('dockAddedPage');
135        if (newPageList && newPageList?.length !== this.oldPageList?.length) {
136          PageDesktopViewModel.getInstance().deleteBlankPage(newPageList[newPageList.length - 1]);
137        }
138        const dragResult = SmartDockDragHandler.getInstance().onDragDrop(event.getWindowX(), event.getWindowY());
139        AppStorage.setOrCreate('selectAppIndex', null);
140        const dragItemInfo: LauncherDragItemInfo = AppStorage.get('dragItemInfo') as LauncherDragItemInfo;
141        if (dragItemInfo.bundleName) {
142          BadgeManager.getInstance().updateBadgeNumber(dragItemInfo.bundleName, dragItemInfo.badgeNumber);
143        }
144        if (!dragResult) {
145          AppStorage.setOrCreate<LauncherDragItemInfo>('dragItemInfo', new LauncherDragItemInfo());
146        } else {
147          // Wait for the UI rendering to end.
148          setTimeout(() => {
149            AppStorage.setOrCreate<LauncherDragItemInfo>('dragItemInfo', new LauncherDragItemInfo());
150          }, 50);
151        }
152      })
153    }
154  }
155}
156
157@Component
158struct AppItem {
159  @StorageLink('dragItemInfo') smartDragItemInfo: LauncherDragItemInfo = new LauncherDragItemInfo();
160  @StorageLink('dragItemType') dragItemType: number = CommonConstants.DRAG_FROM_DOCK;
161  @State isShow: boolean = false;
162  onItemClick: (event: ClickEvent, item: DockItemInfo) => void = (event: ClickEvent, item: DockItemInfo) => {};
163  appInfo: DockItemInfo = new DockItemInfo();
164  buildMenu: (item: DockItemInfo) => MenuInfo[] = (item: DockItemInfo): MenuInfo[] => [];
165  private menuInfo: MenuInfo[] = [];
166
167  aboutToAppear(): void {
168    this.menuInfo = this.buildMenu(this.appInfo);
169  }
170
171  aboutToDisappear(): void {
172    this.isShow = false;
173    this.menuInfo = [];
174  }
175
176  private getLongPress(): boolean {
177    return AppStorage.get('isLongPress') as boolean;
178  }
179
180  @Builder MenuBuilder() {
181    Column() {
182      AppMenu({
183        menuInfoList: this.menuInfo,
184        closeMenu: () => {
185          this.isShow = false;
186        }
187      })
188    }
189    .alignItems(HorizontalAlign.Center)
190    .justifyContent(FlexAlign.Center)
191    .width(StyleConstants.CONTEXT_MENU_WIDTH)
192    .height(StyleConstants.DEFAULT_40 * this.menuInfo.length + StyleConstants.DEFAULT_8)
193  }
194
195  build() {
196    Column() {
197      AppIcon({
198        iconSize: mSmartDockStyleConfig?.mIconSize as number,
199        iconId: this.appInfo.appIconId,
200        bundleName: this.appInfo.bundleName,
201        moduleName: this.appInfo.moduleName,
202        icon: ResourceManager.getInstance().getCachedAppIcon(this.appInfo.appIconId,
203        this.appInfo.bundleName, this.appInfo.moduleName),
204        badgeNumber: this.appInfo.badgeNumber
205      })
206    }
207    .visibility(this.dragItemType === CommonConstants.DRAG_FROM_DOCK &&
208      this.smartDragItemInfo.keyName === this.appInfo.keyName ?
209    Visibility.Hidden : Visibility.Visible)
210    .width(mSmartDockStyleConfig?.mListItemWidth as number)
211    .height(mSmartDockStyleConfig?.mListItemHeight as number)
212    .backgroundColor(mSmartDockStyleConfig?.mItemBackgroundColor as string)
213    .borderRadius(mSmartDockStyleConfig?.mItemBorderRadius as number)
214    .parallelGesture(
215    LongPressGesture({ repeat: false })
216      .onAction((event: GestureEvent) => {
217        Log.showInfo(TAG, 'onAction start');
218        this.isShow = true;
219        AppStorage.setOrCreate('isLongPress', true);
220      })
221    )
222    .bindPopup(this.isShow, {
223      builder: this.MenuBuilder,
224      placement: Placement.Top,
225      popupColor: Color.White,
226      arrowOffset: AppStorage.get('deviceType') == CommonConstants.DEFAULT_DEVICE_TYPE
227        ? null : 3 * ((mSmartDockStyleConfig?.mIconSize as number) / 2) +
228          (mSmartDockStyleConfig?.mListItemGap as number), // Avoid the popup offset problem in phone form
229      onStateChange: (e) => {
230        if (!e.isVisible) {
231          this.isShow = false;
232        }
233        AppStorage.setOrCreate('smartDockShowMenu', e.isVisible)
234      },
235      autoCancel: true
236    })
237    .onTouch((event: TouchEvent) => {
238      Log.showInfo(TAG, `onTouch event type: ${event.type}`);
239      if (event.type === CommonConstants.TOUCH_TYPE_UP && this.smartDragItemInfo.isDragging) {
240        let mIsDragEffectArea =
241        SmartDockDragHandler.getInstance().isDragEffectArea(event.touches[0].windowX, event.touches[0].windowY);
242        if (!mIsDragEffectArea) {
243          AppStorage.setOrCreate<LauncherDragItemInfo>('dragItemInfo', new LauncherDragItemInfo());
244          AppStorage.setOrCreate('selectAppIndex', null);
245        }
246        AppStorage.setOrCreate('isLongPress', false);
247      }
248      if (this.smartDragItemInfo.isDragging) {
249        this.isShow = false;
250      }
251    })
252    .onClick((event) => {
253      this.onItemClick(event, this.appInfo);
254    })
255    .onMouse((event: MouseEvent) => {
256      Log.showInfo(TAG, `onMouse MouseType: ${event.button}`);
257      if (event.button == MouseButton.Right) {
258        event.stopPropagation();
259        AppStorage.setOrCreate('selectDesktopAppItem', '');
260        this.isShow = true;
261      }
262    })
263    .onDragStart((event: DragEvent, extraParams: string) => {
264      Log.showInfo(TAG, 'DragStart');
265      this.dragItemType = CommonConstants.DRAG_FROM_DOCK;
266      this.appInfo.isDragging = true;
267      this.smartDragItemInfo = this.appInfo as LauncherDragItemInfo;
268      Log.showInfo(TAG, `smartDragItemInfo: ${JSON.stringify(this.smartDragItemInfo)}`);
269      const selectAppIndex =
270      SmartDockDragHandler.getInstance().getDragItemIndexByCoordinates(event.getWindowX(), event.getWindowY());
271      AppStorage.setOrCreate('selectAppIndex', selectAppIndex);
272    })
273    .onDragEnd((event: DragEvent, extraParams: string) => {
274      Log.showInfo(TAG, `onDragEnd event: [${event.getWindowX()}, ${event.getWindowY()}]` + event.getResult());
275      AppStorage.setOrCreate<LauncherDragItemInfo>('dragItemInfo', new LauncherDragItemInfo());
276    })
277  }
278}