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  ScrollerComponent,
20} from '@ohos/common/component'
21import {
22  LauncherDragItemInfo,
23  DockItemInfo,
24  CommonConstants,
25  StyleConstants,
26  ResourceManager,
27  Log,
28  MenuInfo
29} from '@ohos/common';
30import { SmartDockStyleConfig } from '../config/SmartDockStyleConfig';
31
32let mSmartDockStyleConfig: SmartDockStyleConfig | null = null;
33const TAG = 'RecentLayout';
34
35interface DockRecentPopup {
36  show: boolean;
37  showItem: string;
38  popup: CustomPopupOptions | null;
39}
40
41interface DockPadding {
42  right: number;
43  left: number;
44  top: number;
45  bottom: number;
46}
47
48@Component
49export default struct RecentLayout {
50  @StorageLink('sysUiRecentOnClickEvent') @Watch('sysUiRecentOnClick') sysUiRecentOnClickEvent: number = 0;
51  @StorageLink('dockPadding') dockPadding: DockPadding = {right: 0, left: 0, top: 0, bottom: 0};
52  @State isHover: boolean = false;
53  @State showPopup: boolean = false;
54  @State onHoverItem: string = '';
55  @Link @Watch('onDockListChange') appList: Array<DockItemInfo>;
56  mRecentMaxNum: number = 0;
57  @Link mSmartDockStyleConfig: SmartDockStyleConfig;
58  onItemClick: Function = () => {};
59  buildMenu: (item: DockItemInfo) => MenuInfo[] = (item: DockItemInfo): MenuInfo[] => [];
60  onHoverEvent: Function = () => {};
61  onDockListChangeFunc: Function = () => {};
62  isScrollHover: boolean = false;
63  popup: DockRecentPopup = { show: false, showItem: '', popup: null };
64  onClickWithPopup: boolean = false;
65  autoCancel: boolean = false;
66  private updateData: Function = () => {};
67
68  aboutToAppear(): void {
69    mSmartDockStyleConfig = this.mSmartDockStyleConfig;
70  }
71
72  aboutToDisappear(): void {
73    this.isHover = false;
74    this.showPopup = false;
75    this.onHoverItem = '';
76    this.isScrollHover = false;
77    this.onClickWithPopup = false;
78    this.autoCancel = false;
79  }
80
81  private sysUiRecentOnClick() {
82    this.showPopup = false;
83    this.popup = { show: false, showItem: '', popup: null };
84  }
85
86  @Builder popupBuilder() {
87    Column() {
88      ScrollerComponent({
89        popupHide: () => {
90          this.showPopup = false;
91          this.popup = { show: false, showItem: '', popup: null };
92        },
93        updateData: (show: boolean, bundleName: string, callback: () => void) => {
94          this.updateData = () => {
95            callback();
96            setTimeout(() => {
97              this.onHoverEvent(true, bundleName);
98            }, 100)
99          }
100          if (show) {
101            this.updateData();
102            this.getShowPopup();
103            return;
104          }
105          this.showPopup = false;
106          this.popup = { show: false, showItem: '', popup: null };
107        }
108      })
109    }.onHover((isScrollHover: boolean) => {
110      this.autoCancel = false;
111      if (isScrollHover) {
112        this.isScrollHover = true;
113      } else {
114        this.isScrollHover = false;
115      }
116      this.getShowPopup();
117    })
118    .onClick(() => {
119      this.getShowPopup();
120    })
121  }
122
123  async getShowPopup() {
124    await this.delay(500);
125    if (this.popup.show || this.isScrollHover) {
126      this.showPopup = true;
127    } else {
128      this.showPopup = false;
129    }
130    return this.showPopup;
131  }
132
133  delay(ms: number) {
134    return new Promise<PromiseConstructor>(resolve => setTimeout(resolve, ms));
135  }
136
137  build() {
138    List({ space: (mSmartDockStyleConfig?.mListItemGap as number) }) {
139      ForEach(this.appList, (item: DockItemInfo) => {
140        ListItem() {
141          AppItem({
142            appInfo: item,
143            buildMenu: this.buildMenu
144          })
145        }
146        .bindPopup(this.showPopup && item.bundleName == this.onHoverItem && !AppStorage.get('smartDockShowMenu') as boolean, {
147          builder: this.popupBuilder,
148          placement: Placement.Top,
149          enableArrow: true,
150          autoCancel: this.autoCancel,
151          maskColor: ('rgba(250,250,250,0)'),
152          popupColor: ('rgba(250,250,250,0.6)'),
153          onStateChange: (e) => {
154            if (!e.isVisible && this.autoCancel) {
155              this.popup = { show: false, showItem: '', popup: null };
156              this.onHoverItem = '';
157              this.onClickWithPopup = false;
158              this.autoCancel = false;
159              this.showPopup = false;
160              AppStorage.setOrCreate('snapshotList', []);
161              AppStorage.setOrCreate('recentShowPopup', false);
162            }
163            if (this.updateData) {
164              this.updateData();
165              this.updateData = () => {
166              }
167            }
168          },
169        })
170        .onHover((isHover) => {
171          this.autoCancel = false;
172          if (this.onHoverEvent) {
173            this.onHoverEvent(isHover, item.bundleName);
174            this.onHoverItem = item.bundleName as string;
175            this.getShowPopup();
176          }
177        })
178        .onClick((event: ClickEvent) => {
179          this.onItemClick(event, item);
180          this.onClickWithPopup = AppStorage.get('recentShowPopup') as boolean;
181          Log.showInfo(TAG, `onClick this.onClickWithPopup: ${this.onClickWithPopup}`);
182          if (this.onClickWithPopup) {
183            this.autoCancel = true;
184            this.showPopup = true
185            this.onHoverItem = item.bundleName as string;
186          }
187          AppStorage.setOrCreate('recentShowPopup', false);
188        })
189      }, (item: DockItemInfo) => JSON.stringify(item))
190    }
191    .enableScrollInteraction(false)
192    .scrollBar(BarState.Off)
193    .padding(this.dockPadding)
194    .width(this.getListWidth())
195    .height(this.mSmartDockStyleConfig?.mDockHeight as number)
196    .backgroundColor(this.mSmartDockStyleConfig?.mBackgroundColor as string)
197    .borderRadius(this.mSmartDockStyleConfig?.mDockRadius as number)
198    .backdropBlur(this.mSmartDockStyleConfig?.mBackdropBlur as number)
199    .visibility(this.getListWidth() === 0 ? Visibility.None : Visibility.Visible)
200    .listDirection(Axis[(this.mSmartDockStyleConfig?.mListDirection as string)])
201  }
202
203  getListWidth(): number {
204    let mRecentMaxNum = this.mSmartDockStyleConfig?.mMaxRecentNum as number;
205    let width = 0;
206    if (AppStorage.get('deviceType') == CommonConstants.DEFAULT_DEVICE_TYPE) {
207      return width;
208    }
209    if (typeof this.appList === 'undefined' || this.appList == null || this.appList.length === 0) {
210      return width;
211    }
212    let num = this.appList.length;
213    if (num > mRecentMaxNum) {
214      num = mRecentMaxNum;
215    }
216    width = (this.mSmartDockStyleConfig?.mDockPadding as number) * 2 +
217      num * (this.mSmartDockStyleConfig?.mListItemWidth as number) +
218      (num - 1) * (this.mSmartDockStyleConfig?.mListItemGap as number);
219    return width;
220  }
221
222  private onDockListChange() {
223    this.onDockListChangeFunc();
224  }
225}
226
227@Component
228struct AppItem {
229  @State isShow: boolean = false;
230  appInfo: DockItemInfo = new DockItemInfo();
231  buildMenu: (item: DockItemInfo) => MenuInfo[] = (item: DockItemInfo): MenuInfo[] => [];
232  private menuInfo: MenuInfo[] = [];
233
234  aboutToAppear(): void {
235    this.menuInfo = this.buildMenu(this.appInfo);
236  }
237
238  aboutToDisappear(): void {
239    this.isShow = false;
240  }
241
242  private getLongPress(): boolean {
243    return AppStorage.get('isLongPress') as boolean;
244  }
245
246  @Builder MenuBuilder() {
247    Column() {
248      AppMenu({
249        menuInfoList: this.menuInfo,
250        closeMenu: () => {
251          this.isShow = false;
252        }
253      })
254    }
255    .alignItems(HorizontalAlign.Center)
256    .justifyContent(FlexAlign.Center)
257    .width(StyleConstants.CONTEXT_MENU_WIDTH)
258    .height(StyleConstants.DEFAULT_40 * this.menuInfo.length + StyleConstants.DEFAULT_8)
259  }
260
261  build() {
262    Column() {
263      Row() {
264        AppIcon({
265          iconSize: mSmartDockStyleConfig?.mIconSize as number,
266          iconId: this.appInfo.appIconId as number,
267          bundleName: this.appInfo.bundleName as string,
268          moduleName: this.appInfo.moduleName as string,
269          icon: ResourceManager.getInstance().getCachedAppIcon(this.appInfo.appIconId as number,
270            this.appInfo.bundleName as string, this.appInfo.moduleName as string),
271          badgeNumber: this.appInfo.badgeNumber as number
272        })
273      }
274      .width(mSmartDockStyleConfig?.mListItemWidth as number)
275      .height(mSmartDockStyleConfig?.mListItemHeight as number)
276      .backgroundColor(mSmartDockStyleConfig?.mItemBackgroundColor as string)
277      .borderRadius(mSmartDockStyleConfig?.mItemBorderRadius as number)
278    }
279    .gesture(
280    LongPressGesture({ repeat: false })
281      .onAction((event: GestureEvent) => {
282        this.isShow = true;
283        AppStorage.setOrCreate('isLongPress', true);
284      })
285    )
286    .bindPopup(this.isShow, {
287      builder: this.MenuBuilder,
288      placement: Placement.Top,
289      popupColor: Color.White,
290      arrowOffset: 3 * ((mSmartDockStyleConfig?.mIconSize as number) / 2) +
291        (mSmartDockStyleConfig?.mListItemGap as number),
292      onStateChange: (e) => {
293        if (!e.isVisible) {
294          this.isShow = false;
295        }
296        AppStorage.setOrCreate('smartDockShowMenu', e.isVisible)
297      },
298      autoCancel: true
299    })
300    .onTouch((event: TouchEvent) => {
301      if (event.type == CommonConstants.TOUCH_TYPE_UP) {
302        AppStorage.setOrCreate('isLongPress', false);
303      }
304      const dragItemInfo: LauncherDragItemInfo = AppStorage.get<LauncherDragItemInfo>('dragItemInfo') as LauncherDragItemInfo;
305      if (dragItemInfo.isDragging) {
306        this.isShow = false;
307      }
308    })
309    .onMouse((event: MouseEvent) => {
310      Log.showInfo(TAG, `onMouse MouseType: ${event.button}`);
311      if (event.button == MouseButton.Right) {
312        event.stopPropagation();
313        AppStorage.setOrCreate('selectDesktopAppItem', '');
314        this.isShow = true;
315      }
316    })
317  }
318}