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 { LauncherDragItemInfo, Log, RecentBundleMissionInfo, ResourceManager } from '@ohos/common';
17import { Trace } from '@ohos/common';
18import { CheckEmptyUtils } from '@ohos/common';
19import { MenuInfo } from '@ohos/common';
20import { MissionInfo } from '@ohos/common';
21import { DockItemInfo } from '@ohos/common';
22import { windowManager } from '@ohos/common';
23import { layoutConfigManager } from '@ohos/common';
24import { amsMissionManager }from '@ohos/common';
25import { launcherAbilityManager } from '@ohos/common';
26import { CommonConstants } from '@ohos/common';
27import { BaseViewModel } from '@ohos/common';
28import SmartDockStartAppHandler from '../common/SmartDockStartAppHandler';
29import SmartDockModel from '../model/SmartDockModel';
30import SmartDockDragHandler from '../common/SmartDockDragHandler';
31import { SmartDockStyleConfig } from '../config/SmartDockStyleConfig';
32import SmartDockConstants from '../common/constants/SmartDockConstants';
33import { SmartDockLayoutConfig } from '../config/SmartDockLayoutConfig';
34
35const TAG = 'SmartDockViewModel';
36
37class StartAppItemInfo extends LauncherDragItemInfo {
38  icon?: ResourceStr;
39}
40
41/**
42 * SmartDock Viewmodel
43 */
44export default class SmartDockViewModel extends BaseViewModel {
45  private readonly mSmartDockLayoutConfig: SmartDockLayoutConfig;
46  private readonly mSmartDockStyleConfig: SmartDockStyleConfig;
47  private readonly mSmartDockDragHandler: SmartDockDragHandler;
48  private readonly mSmartDockStartAppHandler: SmartDockStartAppHandler;
49  private readonly mSmartDockModel: SmartDockModel;
50  private mSelectedItem: DockItemInfo | null = null;
51  private mSelectedDockType = 0;
52  private mDevice = CommonConstants.DEFAULT_DEVICE_TYPE;
53
54  constructor() {
55    super();
56    this.mSmartDockLayoutConfig = layoutConfigManager.getFunctionConfig(SmartDockLayoutConfig.SMART_DOCK_LAYOUT_INFO);
57    this.mSmartDockStyleConfig = layoutConfigManager.getStyleConfig(SmartDockStyleConfig.APP_LIST_STYLE_CONFIG, SmartDockConstants.FEATURE_NAME);
58    this.mSmartDockDragHandler = SmartDockDragHandler.getInstance();
59    this.mSmartDockStartAppHandler = SmartDockStartAppHandler.getInstance();
60    this.mSmartDockModel = SmartDockModel.getInstance();
61  }
62
63  static getInstance(): SmartDockViewModel{
64    if (globalThis.SmartDockViewModel == null) {
65      globalThis.SmartDockViewModel = new SmartDockViewModel();
66    }
67    return globalThis.SmartDockViewModel;
68  }
69
70  /**
71   * get SmartDockStyleConfig
72   */
73  getStyleConfig(): SmartDockStyleConfig{
74    return layoutConfigManager.getStyleConfig(SmartDockStyleConfig.APP_LIST_STYLE_CONFIG, SmartDockConstants.FEATURE_NAME);
75  }
76
77  /**
78   * resident dock item onClick function
79   * @param event
80   * @param item
81   * @param callback
82   */
83  residentOnClick(event: ClickEvent | null, item: DockItemInfo, callback?: () => void) {
84    // AppCenter entry
85    AppStorage.setOrCreate('startAppTypeFromPageDesktop', CommonConstants.OVERLAY_TYPE_APP_RESIDENTIAL);
86    if (item.abilityName == CommonConstants.APPCENTER_ABILITY && callback != null) {
87      callback();
88      return;
89    }
90    if (item.abilityName == CommonConstants.RECENT_ABILITY) {
91      windowManager.createWindowWithName(windowManager.RECENT_WINDOW_NAME, windowManager.RECENT_RANK);
92      Trace.start(Trace.CORE_METHOD_START_RECENTS);
93      return;
94    }
95    // app entry
96    Trace.start(Trace.CORE_METHOD_START_APP_ANIMATION);
97    this.setStartAppInfo(item as StartAppItemInfo);
98    launcherAbilityManager.startLauncherAbility(item.abilityName, item.bundleName, item.moduleName);
99  }
100
101  /**
102   * recent dock item onClick function
103   * @param event
104   * @param item
105   */
106  public recentOnClick(event: ClickEvent | null, item: DockItemInfo, callback?: () => void) {
107    AppStorage.setOrCreate('startAppTypeFromPageDesktop', CommonConstants.OVERLAY_TYPE_APP_RECENT);
108    let missionInfoList: RecentBundleMissionInfo[] = [];
109    missionInfoList = AppStorage.get('missionInfoList');
110    Log.showDebug(TAG, `recentOnClick missionInfoList.length: ${missionInfoList.length}`);
111    if (!CheckEmptyUtils.isEmptyArr(missionInfoList)) {
112      for (let i = 0; i < missionInfoList.length; i++) {
113        if (missionInfoList[i].bundleName === item.bundleName) {
114          let missionList = missionInfoList[i]?.missionInfoList;
115          Log.showDebug(TAG, 'recentOnClick missionList.length:' + missionList.length);
116          if (!CheckEmptyUtils.isEmptyArr(missionList) && missionList.length > 1) {
117            Log.showDebug(TAG, 'recentOnClick callback');
118            callback();
119          } else if (!CheckEmptyUtils.isEmptyArr(missionList) && missionList.length === 1) {
120            let missionId = missionInfoList[i]?.missionInfoList[0]?.missionId;
121            amsMissionManager.moveMissionToFront(missionId).then(() => {}, () => {});
122            // set start app info
123            Trace.start(Trace.CORE_METHOD_START_APP_ANIMATION);
124            this.setStartAppInfo(item as StartAppItemInfo);
125          }
126          break;
127        }
128      }
129    }
130  }
131
132  /**
133   * what SmartDockContent.dockItemList onChange
134   */
135  onDockListChange() {
136    this.updateDockParams().then(() => {}, () => {});
137  }
138
139  /**
140   * update drag effective area when dockList changed
141   */
142  async updateDockParams() {
143    const screenWidth: number = AppStorage.get('screenWidth') as number;
144    const screenHeight: number = AppStorage.get('screenHeight') as number;
145    const sysUIBottomHeight: number = AppStorage.get('sysUIBottomHeight') as number;
146    const dockHeight: number = AppStorage.get('dockHeight') as number;
147    let mResidentWidth: number = this.getListWidth(AppStorage.get('residentList'));
148    if ((AppStorage.get('deviceType') as string) === CommonConstants.DEFAULT_DEVICE_TYPE) {
149      const maxDockNum = this.getStyleConfig().mMaxDockNum;
150      mResidentWidth = this.mSmartDockStyleConfig.mDockPadding * 2 +
151        maxDockNum * (this.mSmartDockStyleConfig.mListItemWidth) +
152        (maxDockNum - 1) * (this.mSmartDockStyleConfig.mListItemGap);
153    }
154    AppStorage.setOrCreate('residentWidth', mResidentWidth);
155    AppStorage.setOrCreate("dockPadding", this.getDockPadding(mResidentWidth));
156    const mRecentWidth: number = this.getListWidth(AppStorage.get('recentList'));
157    Log.showDebug(TAG, `updateDockParams screenWidth:${screenWidth}, screenHeight:${screenHeight}, sysUIBottomHeight:${sysUIBottomHeight}, dockHeight:${dockHeight}, mResidentWidth:${mResidentWidth}, mRecentWidth:${mRecentWidth}`);
158    if (typeof (this.mSmartDockDragHandler) != 'undefined') {
159      let left = mResidentWidth === 0 ? 0 : (screenWidth - mResidentWidth - (mRecentWidth === 0 ? 0 : (this.mSmartDockStyleConfig.mDockGap + mRecentWidth))) / 2;
160      let right = mResidentWidth === 0 ? screenWidth : (screenWidth - mResidentWidth - (mRecentWidth === 0 ? 0 : (this.mSmartDockStyleConfig.mDockGap + mRecentWidth))) / 2 + mResidentWidth;
161      if ((AppStorage.get('deviceType') as string) == CommonConstants.DEFAULT_DEVICE_TYPE) {
162        left = (screenWidth - mResidentWidth) / 2;
163        right = screenWidth - left;
164      }
165      this.mSmartDockDragHandler.setDragEffectArea({
166        left: left,
167        right: right,
168        top: screenHeight - sysUIBottomHeight - dockHeight,
169        bottom: screenHeight - sysUIBottomHeight - this.mSmartDockStyleConfig.mMarginBottom
170      });
171    }
172  }
173
174  private getDockPadding(residentWidth: number): {right: number, left: number, top: number, bottom: number} {
175    let paddingNum: number = this.mSmartDockStyleConfig.mDockPadding;
176    const residentList: [] = AppStorage.get('residentList');
177    if (AppStorage.get("deviceType") === CommonConstants.DEFAULT_DEVICE_TYPE) {
178      paddingNum = (residentWidth - (residentList.length * this.mSmartDockStyleConfig.mListItemWidth + (residentList.length - 1) * (this.mSmartDockStyleConfig.mListItemGap))) / 2;
179    }
180    Log.showDebug(TAG, `getDockPadding paddingNum: ${paddingNum}`);
181    return {right: paddingNum, left: paddingNum, top: this.mSmartDockStyleConfig.mDockPadding, bottom: this.mSmartDockStyleConfig.mDockPadding};
182  }
183
184  /**
185   * build menu for @Component CustomOverlay
186   * @param appInfo
187   * @param dockType
188   * @param callback
189   */
190  buildMenuInfoList(appInfo: DockItemInfo, dockType: number, showAppcenter: () => void, callback?: () => void) {
191    const menuInfoList = new Array<MenuInfo>();
192    const shortcutInfo = this.mSmartDockModel.getShortcutInfo(appInfo.bundleName);
193    if (shortcutInfo) {
194      let menu: MenuInfo | null = null;
195      shortcutInfo.forEach((value) => {
196        menu = new MenuInfo();
197        menu.menuType = CommonConstants.MENU_TYPE_DYNAMIC;
198        menu.menuImgSrc = value.icon;
199        menu.menuText = value.label;
200        menu.shortcutIconId = value.iconId;
201        menu.shortcutLabelId = value.labelId;
202        menu.bundleName = value.bundleName;
203        menu.moduleName = value.moduleName;
204        menu.onMenuClick = () => {
205          Trace.start(Trace.CORE_METHOD_START_APP_ANIMATION);
206          launcherAbilityManager.startLauncherAbility(value.wants[0].targetAbility,
207            value.wants[0].targetBundle, value.wants[0].targetModule);
208        };
209        value.bundleName == appInfo.bundleName && value.moduleName == appInfo.moduleName && menuInfoList.push(menu);
210      });
211    }
212
213    let open = new MenuInfo();
214    open.menuType = CommonConstants.MENU_TYPE_FIXED;
215    open.menuImgSrc = '/common/pics/ic_public_add_norm.svg';
216    open.menuText = $r('app.string.app_menu_open');
217    open.onMenuClick = () => {
218      this.residentOnClick(null, appInfo, showAppcenter);
219    };
220    menuInfoList.push(open);
221
222    if (appInfo.itemType != CommonConstants.TYPE_FUNCTION) {
223      this.mDevice = AppStorage.get('deviceType') as string;
224      if (this.mDevice === CommonConstants.PAD_DEVICE_TYPE && dockType === SmartDockConstants.RESIDENT_DOCK_TYPE) {
225        const addToWorkSpaceMenu = new MenuInfo();
226        addToWorkSpaceMenu.menuType = CommonConstants.MENU_TYPE_FIXED;
227        addToWorkSpaceMenu.menuImgSrc = '/common/pics/ic_public_copy.svg';
228        addToWorkSpaceMenu.menuText = $r('app.string.app_center_menu_add_desktop');
229        addToWorkSpaceMenu.onMenuClick = () => {
230          Log.showDebug(TAG, 'onMenuClick item add to pageDesk:' + appInfo.bundleName);
231          this.mSmartDockModel.addToPageDesk(appInfo);
232        };
233        menuInfoList.push(addToWorkSpaceMenu);
234      }
235
236      const removeMenu = new MenuInfo();
237      removeMenu.menuType = CommonConstants.MENU_TYPE_FIXED;
238      removeMenu.menuImgSrc = this.mDevice === CommonConstants.PAD_DEVICE_TYPE ? '/common/pics/ic_public_remove.svg' : '/common/pics/ic_public_delete.svg';
239      removeMenu.menuText = this.mDevice === CommonConstants.PAD_DEVICE_TYPE ? $r('app.string.delete_app') : $r('app.string.uninstall');
240      removeMenu.onMenuClick = () => {
241        Log.showDebug(TAG, `onMenuClick item remove: ${JSON.stringify(appInfo)}`);
242        const cacheKey = appInfo.appLabelId + appInfo.bundleName + appInfo.moduleName;
243        appInfo.keyName = appInfo.bundleName + appInfo.abilityName + appInfo.moduleName;
244        const appName = this.mSmartDockModel.getAppName(cacheKey);
245        Log.showDebug(TAG, `onMenuClick item remove appName: ${appName}`);
246        if (appName != null) {
247          appInfo.appName = appName;
248        }
249        this.mSelectedItem = appInfo;
250        this.mSelectedDockType = dockType;
251        AppStorage.setOrCreate('uninstallAppInfo', appInfo);
252        callback();
253      };
254      removeMenu.menuEnabled = appInfo.isUninstallAble;
255      menuInfoList.push(removeMenu);
256    }
257    return menuInfoList;
258  }
259
260  deleteDockItem(dockItem: DockItemInfo, dockType: number) {
261    this.mSmartDockModel.deleteDockItem(dockItem, dockType);
262  }
263
264  getSelectedItem(): DockItemInfo | null {
265    Log.showDebug(TAG, `getSelectedItem: ${JSON.stringify(this.mSelectedItem)}`);
266    return this.mSelectedItem;
267  }
268
269  getSelectedDockType(): number {
270    Log.showDebug(TAG, `getSelectedDockType: ${JSON.stringify(this.mSelectedDockType)}`);
271    return this.mSelectedDockType;
272  }
273
274  /**
275   * calcaulate dock list width after list change
276   * @param itemList
277   */
278  private getListWidth(itemList: RecentBundleMissionInfo[]): number {
279    let width = 0;
280    if (typeof itemList === 'undefined' || itemList == null || itemList.length === 0) {
281      return width;
282    }
283    const num =  itemList.length;
284    width = this.mSmartDockStyleConfig.mDockPadding * 2 + num * (this.mSmartDockStyleConfig.mListItemWidth) + (num - 1) * (this.mSmartDockStyleConfig.mListItemGap);
285    return width;
286  }
287
288  /**
289   * get snapshot
290   *
291   * @param missionIds missionid list
292   * @return snapshot list
293   */
294  async getSnapshot(missionIds: MissionInfo[], name: string) {
295    const snapshotList: {
296      name: string,
297      image: any,
298      missionId: number,
299      boxSize: number,
300      bundleName: string,
301      left?: number,
302      right?: number,
303    }[] = await this.mSmartDockModel.getSnapshot(missionIds, name);
304    return snapshotList;
305  }
306
307  /**
308   * set start app info
309   */
310  private setStartAppInfo(item: StartAppItemInfo) {
311    if (CheckEmptyUtils.isEmpty(item)) {
312      Log.showError(TAG, 'setStartAppInfo with item')
313      return;
314    }
315    item.icon = ResourceManager.getInstance().getCachedAppIcon(item.appIconId, item.bundleName, item.moduleName)
316    AppStorage.setOrCreate('startAppItemInfo', item);
317    this.mSmartDockStartAppHandler.setAppIconSize(this.mSmartDockStyleConfig.mIconSize);
318    this.mSmartDockStartAppHandler.setAppIconInfo();
319  }
320}